【问题标题】:Rust impl default trait with private fieldsRust 实现带有私有字段的默认特征
【发布时间】:2020-12-19 08:18:19
【问题描述】:

当我进行这种设置时出现错误:

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

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

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

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

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

我有什么选择?

【问题讨论】:

  • 如何将您的公共字段移动到单独的类型:pub struct Data { pub x: i32, pub y: i32}; pub struct Point { pub data: Data, z: i32 };,然后执行fn new_with_data(data: Data)?您的布局略有变化,但应该可以工作。
  • 我其实挺喜欢这个主意的,我会考虑考虑的

标签: rust default private public


【解决方案1】:

我有什么选择?

带有参数或构建器的new()

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

【讨论】:

  • 我认为 OP 没想到 ..struct 会绕过 ACL,而是从 struct 开始并更新明确指定的字段,在 OP 的情况下是公共 z
  • 但这不是他们正在做的事情,他们正在做的是创建一个新的结构来复制旧的结构,同时替换其中一个字段的值。
  • 同意,只是OP对struct-update语法的解释与其实际含义不同。因为我记得当我第一次遇到 struct-update 语法与私有字段的交互时遇到过类似的困惑,所以我试图在我的回答中涵盖它。
【解决方案2】:

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

问题在于 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.. 后面可以跟一个返回结构的任意表达式,这就是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
};

我有什么选择?

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


1 虽然有像 derive_buildertyped-builderbuilder-pattern 这样的板条箱,可以消除一些苦差事。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-03-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-05-29
    相关资源
    最近更新 更多