Skip to content

Instantly share code, notes, and snippets.

@StevenBlack
Forked from ChrisWellsWood/empty_rust_structs.md
Last active October 17, 2020 01:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save StevenBlack/a24145858d7ecdb3e1f65db7c816988d to your computer and use it in GitHub Desktop.
Save StevenBlack/a24145858d7ecdb3e1f65db7c816988d to your computer and use it in GitHub Desktop.
Initialising empty structs in Rust.

Initializing Empty Structs in Rust

In C/C++, you can initialise a struct without giving values for any of the fields:

struct Point {
  float x;
  float y;
  float z;
};

int main() {
  Point my_point = {};
}

Structs in RUST can't do this by default, you'll just get an error:

#[derive(Debug)]
struct Point {
    x: i32,
    y: i32,
    z: i32,
}

fn main() {
    let p1 = Point{};
    println!("{:?}", eep);
}
error[E0063]: missing fields `x`, `y`, `z` in initializer of `Point`
 --> src\main.rs:2:15
  |
2 |     let p1 = Point{};
  |              ^^^^^ missing `x`, `y`, `z`

The proper way to do this for a struct in Rust is to implement the Default trait and then you can generate default values easily:

#[derive(Debug)]
struct Point {
    x: i32,
    y: i32,
    z: i32,
}

impl Default for Point {
    fn default () -> Point {
        Point{x: 0, y: 0, z:0}
    }
}
fn main() {
  let p1 = Point::default(); 
  let p2 = Point{ x: 34, ..Default::default() }; // Partial definition of fields
}

You can even do this automatically using the derive attribute.

#[derive(Debug, Default)] // Derive is cool, I have no idea how it works!
struct Point {
    x: i32,
    y: i32,
    z: i32,
}

fn main() {
  let p1 = Point::default();
  let p2 = Point{ x: 34, ..Default::default() };
}

It's like magic!

Initialising empty structs is especially useful when working with an API, where you might give a function a pointer to a struct and the fields are populated for you. If you're working with a RUST API that follows this pattern, we can just use our Default trait implementation to do this, right? Well, that depends on the API. If you're using using the winapi crate, this doesn't work as Default has not been implemented for any of the structs that I've used:

extern crate winapi;

use winapi::windef::RECT;

fn main() {
    let rect = RECT{ ..Default::default() };
    println!("{:?}", rect);
}
error[E0277]: the trait bound `winapi::RECT: std::default::Default`
is not satisfied
 --> src\main.rs:6:24
  |
6 |     let rect = RECT{ ..Default::default() };
  |                        ^^^^^^^^^^^^^^^^ trait `winapi::RECT: std::default::Default` not satisfied

Unfortunately, you're not allowed to implement a trait that you did not define, for a type that you also did not define. So if you're using a struct from an external crate, you can't implement Default for it:

extern crate winapi;

use winapi::windef::RECT;

impl Default for RECT {
    fn default () -> RECT {
        RECT{left: 0, top: 0, right: 0, bottom: 0}
    }
}

fn main() {
    let rect = RECT::default();
    println!("{:?}", rect);
}
error[E0117]: only traits defined in the current crate can be implemented for arbitrary types
 --> src\main.rs:5:1
  |
5 | impl Default for RECT {
  | ^ impl doesn't use types inside crate

That's annoying... So what do you do? There's a couple of things you could do. Firstly, you could wrap the struct as a new type (so you're defining it in your own crate):

extern crate winapi;

use winapi::windef::RECT;

#[derive(Debug)]
struct WrappedRECT{rect: RECT}

impl Default for WrappedRECT {
    fn default () -> WrappedRECT {
        WrappedRECT{rect: RECT{left: 0, top: 0, right: 0, bottom: 0}}
    }
}

fn main() {
    let rect = WrappedRECT::default();
    println!("{:?}", rect);
}

But this is a bit clunky, so I prefer just creating a new trait, and implementing it for the external struct:

extern crate winapi;

use winapi::windef::RECT;

trait Empty<T> {
    fn empty() -> T;
}

impl Empty<RECT> for RECT {
    fn empty() -> RECT {
        RECT{left: 0, top: 0, right: 0, bottom: 0}
    }
}

fn main() {
    let rect = RECT::empty();
    println!("{:?}", rect);
}

It seems a little more transparent, and there's no clash with the name of the method. If you want to be a good citizen, the best way to deal with this is probably to just go and modify the crate you're using, adding derive(Debug) attributes to everything!

Thanks to joshtriplett and yohanesu75 for some extra info.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment