How to make some Struct's fields mandatory to fill and others optional in Rust?

Viewed 3805

I have some basic struct to modeling an item's unit, eg: pcs, box, dozen, etc. But I need to make some fields mandatory to be defined by user and some are not. Here's my implementation with default constructor from Rust's documentation. My problem is that Rust forced all of the field to be defined in the constructor:

pub struct Unit {
    pub name: String,              // this is mandatory. MUST be filled
    pub multiplier: f64,           // this is mandatory. MUST be filled
    pub price_1: Option<f64>,      // this is non-mandatory with default value NONE
    pub price_2: Option<f64>,      // this is non-mandatory with default value NONE
    pub price_3: Option<f64>,      // this is non-mandatory with default value NONE
}

// here I implement the Default just for the prices. 
// If user doesn't fill the name and multiplier field, it will throws an error
// the problem is that Rust forced all of the field to be defined in the constructor
impl Default for Unit {
    fn default() -> Unit {
        Unit {
            price_1: None,
            price_2: None,
            price_3: None,
        }
    }
}


let u = Unit {
          name: String::from("DOZEN"), // user must fill the name field
          multiplier: 20.0, // also the multiplier field
          price_1: Some(25600.0), // this is optional, user doesn't have to define this
          ..Default::default()  // call the default function here to populate the rest
        }
3 Answers

You can detach the default implementation into an external struct, and then make a constructor that just needs the mandatory items:

pub struct Unit {
    pub name: String,              // this is mandatory. MUST be filled
    pub multiplier: f64,           // this is mandatory. MUST be filled
    pub prices: Prices
}

pub struct Prices {
    pub price_1: Option<f64>,      // this is non-mandatory with default value NONE
    pub price_2: Option<f64>,      // this is non-mandatory with default value NONE
    pub price_3: Option<f64>,      // this is non-mandatory with default value NONE
}

impl Default for Prices {
    fn default() -> Prices {
        Prices {
            price_1: Default::default(),
            price_2: Default::default(),
            price_3: Default::default()
        }
    }
}

impl Unit {
    pub fn new(name: String, multiplier: f64) -> Self {
        Unit {name, multiplier, prices: Default::default()}
    }
}

Playground

You could create an associated function, from_name_and_multiplier(), for creating your Unit values with a name and multiplier:

impl Unit {
    pub fn from_name_and_multiplier(name: String, multiplier: f64) -> Self {
        Self {
            name,
            multiplier,
            price_1: None,
            price_2: None,
            price_3: None,
        }
    }
}

This function forces you to provide both name and multiplier. Then, you can use this returned Unit value to initialize the name and multiplier fields of another Unit:

let u = Unit {
  price_1: Some(25600.0),
  ..Unit::from_name_and_multiplier("DOZEN".to_string(), 20.0)
};

Playground: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=1a871a9634d39bf83168c5d51b39b236

You can use #[derive(Builder)].

https://docs.rs/derive_builder/latest/derive_builder/

This crate implements the builder pattern for you. Just apply #[derive(Builder)] to a struct Foo, and it will derive an additional struct FooBuilder with setter-methods for all fields and a build-method — the way you want it.

https://docs.rs/derive_builder/latest/derive_builder/#setters-for-option

#[derive(Builder, Debug, PartialEq)]
struct Lorem {
    #[builder(setter(into, strip_option))]
    pub ipsum: Option<String>,
    #[builder(setter(into, strip_option), default)]
    pub foo: Option<String>,
}

fn main() {
    // `"foo"` will be converted into a `String` automatically.
    let x = LoremBuilder::default().ipsum("foo").build().unwrap();

    assert_eq!(x, Lorem {
        ipsum: Some("foo".to_string()),
        foo: None
    });
}
Related