Rust 使用私有字段实现默认特征

Rust impl default trait with private fields

我在进行此类设置时遇到错误:

default_test.rs:

mod default_mod;

use default_mod::Point;

fn main() {
    let _p1 = Point::new();
    let _p2: Point = Point {
        z: 1,
        ..Default::default()
    };
}

default_mod.rs:

pub struct Point {
    x: i32,
    y: i32,
    pub z: i32,
}

impl Point {
    pub fn new() -> Self {
        Point { x: 0, y: 0, z: 0 }
    }
}

impl Default for Point {
    fn default() -> Self {
        Point { x: 0, y: 0, z: 0 }
    }
}

这给出了编译器错误:

default_test.rs:9:7
  |
9 |     ..Default::default()
  |       ^^^^^^^^^^^^^^^^^^ field `x` is private

error[E0451]: field `y` of struct `default_mod::Point` is private

简短版本 - 我有一个包含 public 和私有字段的结构。我想用默认值初始化这个结构,但有时会覆盖它们。

我似乎无法修复此错误,在 Internet 或文档中也没有看到任何提到此类错误的内容。

这让我感到惊讶,因为我认为一个常见的用例是初始化一个结构,并且该结构的某些成员将是私有的,因此您可以将实现细节隐藏在接口后面。

在我的例子中,私有字段是一个 Vec,因为我有一些逻辑需要从向量中添加或删除内容,所以我想将其设为私有以防止任何人弄乱数据结构。

我有哪些选择?

What are my options here?

一个 new() 带有参数或构建器。

..struct 只是进行功能更新的一种便捷方式,它不会绕过 ACL。由于您的结构具有私有字段,因此用户不能将其作为“裸”结构进行操作,他们必须将其视为很大程度上不透明的类型。

It surprises me, because I'd think a common use-case would be to initialise a struct, and that some members of the struct would be private so you can hide implementation details behind and interface.

问题是 struct update 语法并没有像您想象的那样工作。例如,the book 显示以下代码:

let user2 = User {
    email: String::from("another@example.com"),
    username: String::from("anotherusername567"),
    ..user1
};

..user1语法填充我们没有明确指定的User字段,例如active: user1.active, signin_count: user1.signin_count.. 后面可以跟一个任意表达式,其中 returns 是 Default::default() 发挥作用的结构,其含义与 User::default() 相同,因为 User是期待。但是,脱糖保持不变并归结为分配各个字段,在任何情况下都不会授予对私有字段的特殊访问权限。

回到你的例子,这个代码:

let p = Point {
    z: 1,
    ..Default::default()
};

是语法糖:

let p = {
    let _tmp = Point::default();
    Point {
        x: _tmp.x,
        y: _tmp.y,
        z: 1,
    }
};

而不是预期的:

// NOT what happens
let p = {
    let _tmp = Point::default();
    p.z = 1;
    _tmp
};

What are my options here?

最惯用的选项是为 Point 提供 builder。这也有点笨重 1,所以如果您正在寻找一个简单的解决方案,您也可以使用 Point::default() 并手动设置 z 属性。结构更新语法与具有私有字段的结构不兼容,对您的类型没有用。


1 尽管有像 derive_builder, typed-builder and builder-pattern 这样的板条箱可以减轻一些苦差事。