如何在Rust中使结构体的某些字段必填,而其他字段可选?

9
我有一些基本的结构来模拟项目的单位,例如:个,箱,打等。但是我需要让一些字段成为用户必须定义的,而另一些则不需要。这是我使用 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
        }

3
如果有一些字段不能设置默认值,那么你就不应该有这样的默认设置。 - Netwave
那么强制和非强制混合的字段就不可能存在了吗? - DennyHiu
如果您不设置必填部分,那么它们的值将是什么?请注意,这是不可能的。 - Netwave
我认为我应该强制用户填写必填字段。如果他们没有设置它们,就抛出一个错误或其他什么。这在 Rust 中被认为是反模式吗? - DennyHiu
3个回答

8
您可以将默认实现分离到外部结构中,然后创建一个只需要必填项的构造函数:
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()}
    }
}

游乐场


谢谢。虽然我同意这样更清晰,但有时用户需要启动价格。来自Rust-playground的示例表明,我需要在“struct Prices”中再放置另一个单独的函数来设置价格字段,例如:fn set_prices(price_1: Option<f64>),并在单位初始化后调用它。 unit1.set_prices(..) - DennyHiu
@DennyHiu,实际上你可以拥有这个并且仍然使用其他答案中的相同内容。但无论你觉得哪个更适合你。 - Netwave

3
你可以创建一个关联函数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,
        }
    }
}

这个函数强制你提供名称倍数两个参数。然后,你可以使用返回的Unit值来初始化另一个Unit名称倍数字段:

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


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

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

这个箱子为您实现了建造者模式。只需将#[derive(Builder)] 应用于一个名为Foo的结构体,它就会派生出另一个名为FooBuilder的结构体,其中包含所有字段的setter方法和一个build方法 - 就像您想要的那样。

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
    });
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接