如何在 Rust 中使某些 Struct 的字段强制填写而其他字段是可选的?

我有一些基本结构来建模项目的单位,例如:pcs、box、does 等。但我需要强制用户定义一些字段,而有些则不是。这是我使用 Rust 文档中的 default 构造函数的实现。我的问题是 Rust 强制在构造函数中定义所有字段:

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
        }
stack overflow How to make some Struct's fields mandatory to fill and others optional in Rust?
原文答案
author avatar

接受的答案

您可以创建一个关联函数 from_name_and_multiplier() 来创建具有名称和乘数的 Unit 值:

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,
        }
    }
}

此函数强制您同时提供 namemultiplier 。然后,您可以使用这个返回的 Unit 值来初始化另一个 namemultiplierUnit 字段:

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

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


答案:

作者头像

您可以将默认实现分离到外部结构中,然后创建一个只需要必需项的构造函数:

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

作者头像

你可以使用# [derive(Builder)].

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

这个 crate 为你实现了构建器模式。只需申请# [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
    });
}