rust - 定义一个结构体,其中只能默认某些字段

标签 rust

我正在尝试在 Rust 中创建一个结构,它需要定义一些字段,但不需要定义其他字段。

例如,在下面的示例结构中,只有职业是一个选项。因此,我想同时要求 nameage,但如果不是的话,职业 可以默认为 None已定义。

struct Person {
    name: String,
    age: usize,
    occupation: Option<String>,
}

// should compile
let person_a = Person {
    name: "A".to_string(),
    age: 42,
    ..Default::default(),
}

// should not compile because `age` is not defined
let person_b = Person {
    name: "B".to_string(),
    ..Default::default(),
}

// should compile
let person_c = Person {
    name: "C".to_string(),
    age: 42,
    occupation: "Software engineer".to_string(),
}

这在 Rust 中可能吗?

我不需要坚持使用这个确切的 API。其他 API(包括宏)也是可以接受的。

最佳答案

您描述的语法:

// should not compile because `age` is not defined
let person_b = Person {
    name: "B".to_string(),
    ..Default::default(),
}

不可能实现。结构体要么实现 Default ,或者没有。 .. struct update syntax也仅适用于相同类型的结构。

有多种选项可以为您描述的语义实现方便的语法:


1) 多个 new功能:

impl Person {
    fn new(name: String, age: usize) -> Self {
        Self {name, age, occupation: None}
    }
    fn new_with_occupation(name: String, age: usize, occupation: String) -> Self {
        Self {name, age, occupation: Some(occupation)}
    }
}

使用示例:

let p1 = Person::new("a".to_string(), 1);
let p2 = Person::new_with_occupation("b".to_string(), 2, "o".to_string()); 

2)构建器模式:

#[derive(Default)]
struct PersonBuilder {
    // These fields can be pub or not, depending on your tastes.
    // If they are pub, you can use the .. syntax for this builder
    // instead of / in addition to the field setter methods.
    occupation: Option<String>,
}
impl PersonBuilder {
    fn occupation(self, occupation: String) -> Self {
        Self {
            occupation: Some(occupation),
            ..self
        }
    }
    fn build(self, name: String, age: usize) -> Person {
        Person {name, age, occupation: self.occupation }
    }
}

使用示例:

let p1 = PersonBuilder::default().build("a".to_string(), 1);
let p2 = PersonBuilder::default()
    .occupation("b".to_string())
    .build("o".to_string(), 2);
let p3 = 
    PersonBuilder { 
        occupation: Some("o".to_string()),
        ..Default::default()
    }.build("c".to_string(), 3)

3) 默认字段的子结构 + a Deref impl :

#[derive(Default)]
struct PersonOptions {
    occupation: Option<String>,
}
struct Person {
    options: PersonOptions,
    name: String,
    age: usize
}
impl Deref for Person {
    type Target = PersonOptions;
    fn deref(&self) -> &Self::Target {
        &self.options
    }
}
impl DerefMut for Person {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.options
    }
}

使用示例:

let p1 = Person { 
    name: "a".to_string(),
    age: 1,
    options: Default::default() 
};
let p2 = Person { 
    name: "b".to_string(), 
    age: 2, 
    options: PersonOptions { 
        occupation: Some("o".to_string()),
        ..Default::default()
    } 
};
println!("{:?}", p2.occupation); // works because of `Deref`

摘要:

<表类=“s-表”> <标题> 方法 优点 缺点 推荐用于 <正文> 1) new功能 简单易读。 具有许多字段的结构有多种组合。另外,参数的顺序决定了相应的字段,如果添加字段,这可能会变得难以阅读,并且重构起来很烦人。 具有少量字段/初始化变体的简单结构 2)构建器模式 规模优于 1)如果您有很多字段。 比 1) 更多样板,由于 Builder 在 setter 函数中的移动,可能会导致代码生成不理想。 具有许多可选字段的复杂结构 3) Deref 语法最接近您想要的。 Deref一般是discouraged对于简单的结构,因为它可能会让读者感到困惑。 DerefMut还可能导致借用检查器问题,因为整个结构都被重新借用,而不仅仅是访问的字段。 只有当你有充分的理由解释为什么 1 和 2 不切实际时(但我真的想不出一个)。

关于rust - 定义一个结构体,其中只能默认某些字段,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/77538713/

相关文章:

rust - 使用Arrow/Datafusion/Polars(如python panda的groupby)按列值分区?

build - 改进 Rust 二进制构建时间

rust - 如果尚未设置,如何设置嵌套在多个选项中的值?

rust - macro_rules 是一个普通的宏吗?

reflection - 如何检查 Rust 的编译时是否实现了特征?

module - 使用标准库中的模块可以在我的 crate root 中使用,但不能在模块中使用

generics - 为什么 impl 不在范围内

rust - 捕获外部变量的FnOnce闭包

rust - `crates.io` 中的代码引用与实际 crate 不匹配

tcp - 如何在 Rust 中建立与 kafka 服务器的连接