我正在try 在Rust中创建一个 struct ,该 struct 需要定义一些字段,但不需要定义其他字段.

例如,在下面的示例中,只有occupation是一个选项.因此,我希望同时需要nameage,但是如果没有定义,occupation可以缺省为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(),
}

是不可能实现的. struct 要么实现Default,要么不实现... struct update syntax也只适用于相同类型的 struct .

然而,对于您所描述的semantics,有多种 Select 可以实现方便的语法:


1) Multiple new functions:

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 sub struct for the defaultable Fields + 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`

摘要:

Approach Advantages Disadvantages Recommended for
1) new functions Simple and readable. There are lots of combinations for structs with many fields. Also, the order of the parameters decides the corresponding field, which can become hard to read, and annoying to refactor if a field is added. Simple structs with few fields / initialization variants
2) Builder pattern Scales better than 1) if you have many fields. More boilerplate than 1), can lead to suboptimal code generation due to the moves of the Builder in the setter functions. Complex structs with many optional fields
3) Deref Closest in syntax to what you wanted. Deref is generally discouraged for simple structs because it might be confusing to the reader. DerefMut can also lead to borrow checker issues because the whole struct is reborrowed, not just the accessed field. Only if you have a good reason why 1 and 2 are impractical (I can't really think of one though).

Rust相关问答推荐

"value is never read警告似乎不正确.我应该忽略它吗?

闭包不会发送,即使它只捕获发送变量

取得本地对象字段的所有权

我如何制作一个变异迭代器来锁定内部数据直到删除?

定义只有一些字段可以缺省的 struct

如何将实现多个特征的 struct 传递给接受这些特征为&;mut?

从字节数组转换为字节元组和字节数组时,为什么 Transmute 会对字节重新排序?

根据掩码将 simd 通道设置为 0 的惯用方法?

将泛型中的 Box 转换为 rust 中的 Box

Rust: 目标成员属于哪个"目标家族"的列表是否存在?

当在lambda中通过引用传递时,为什么会出现终身/类型不匹配错误?

我可以在 Rust 中 serde struct camel_case 和 deserde PascalCase

Rust 中的自动取消引用是如何工作的?

按下 Ctrl + C 时优雅地停止命令并退出进程

如何从 x86_64 Mac 构建 M1 Mac?

使用 traits 时,borrow 的值不会存在足够长的时间

rust 中不同类型的工厂函数

如何在 Rust 中将 UTF-8 十六进制值转换为 char?

当特征函数依赖于为 Self 实现的通用标记特征时实现通用包装器

缓存自引用函数导致 Rust