Skip to content

Instantly share code, notes, and snippets.

@myfreax
Forked from ChrisWellsWood/empty_rust_structs.md
Created April 10, 2022 07:59
Show Gist options
  • Save myfreax/cc459454ea09ffd6553e243270ccad3a to your computer and use it in GitHub Desktop.
Save myfreax/cc459454ea09ffd6553e243270ccad3a 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.

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