Skip to content

Instantly share code, notes, and snippets.

@ChrisWellsWood
Last active November 8, 2023 10:05
Show Gist options
  • Star 32 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save ChrisWellsWood/84421854794037e760808d5d97d21421 to your computer and use it in GitHub Desktop.
Save ChrisWellsWood/84421854794037e760808d5d97d21421 to your computer and use it in GitHub Desktop.
Initialising empty structs in Rust.

Initialising 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.

@ChrisWellsWood
Copy link
Author

ChrisWellsWood commented Dec 11, 2016

@ferrouswheel I don't mind not being able to implement an external trait on an external type, but maybe it should be highlighted in the Rust book so people think about default initialisation when they're learning about structs in general, so it's carried over to their applications on libraries. For a while I just assumed you could do it by default, until I tried!

@kstep
Copy link

kstep commented Dec 13, 2016

You can avoid making your Empty trait generic by using Self type:

trait Empty {
    fn empty() -> Self;
}

@rajasankar
Copy link

Thanks for sharing the derive Default. I didnt know that before.

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