在Option内部修改struct字段

13

我在尝试修改一个Option内部结构体的字段时遇到了问题。这是代码:

struct MyStruct {
    field1 : i32,
    field2 : i32,
    // and many more...
}

impl MyStruct {
    pub fn field1(&mut self, field1 : i32) -> &mut Self {
        self.field1 = field1;
        self
    }
}

fn foo() -> Option<MyStruct> {
    None
}

fn bar() -> Option<MyStruct> {
    foo().as_mut().map(|s| s.field1(5))
}

fn main() {
    bar();
}

bar()的主要思想是从另一个返回Option<MyStruct>的函数中获取一个Option<MyStruct>,改变该结构体的一个字段(如果结果不为None),然后返回生成的Option<MyStruct>

该结构体实现了构建器模式,因此我已经使用它。

在这种情况下,我得到以下错误:

test.rs:18:5: 18:40 error: mismatched types:
expected `core::option::Option<MyStruct>`,
    found `core::option::Option<&mut MyStruct>`
(expected struct `MyStruct`,
    found &-ptr) [E0308]
test.rs:18     foo().as_mut().map(|s| s.field1(5))

我也尝试过使用 Option.take() 以及将构建器方法的签名更改为 pub fn field1(mut self, field1 : i32) -> Self,但它们都不起作用。

如何让这段代码起作用?

我知道我可以创建另一个 Option 并将输入的每个字段映射到输出的相应字段,但是 MyStruct 有很多字段,这很繁琐。

4个回答

12

完全没有使用as_mut()的必要。因为你需要返回一个值为MyStruct的选项,所以你可以直接在选项上使用map

不需要使用as_mut(),可以通过map直接返回值为MyStruct的选项。

fn bar() -> Option<MyStruct> {
    foo().map(|mut s| { s.field1(5); s })
}

就是这样。你需要使用mut标记s,原因与你需要对let这样做相同。


这对我不起作用。它一直显示之前的相同错误:类型不匹配。也许是 field1() 的签名有问题? - mbrt
1
你需要使用 |mut s| { s.field1(5); s }。该方法会修改对象,但其返回值 &mut 对于按值返回是无用的。 - user395760
哦,抱歉。你说得对。我不知道为什么使用了类似于构建器的语法。我已经修正了答案。 - Vladimir Matveev
@VladimirMatveev 好的,现在它正在工作,并且只进行了最小的更改。对我来说解决了问题。 - mbrt

8
说实话,我会采取显而易见的方法:
fn bar() -> Option<MyStruct> {
    let mut val = foo();
    if let Some(ref mut s) = val {
        s.field1(5);
    }
    val
}

使用映射比较麻烦,而且我觉得用映射来进行变异很不舒服,我选择显式变异的方式可以产生更好的汇编代码。


是的,这很清晰和有效,但是我想知道为什么map在我使用的方式上没有起作用。 - mbrt

3

既然其他人已经提出更广泛的建议,我想指出你可以改变你的“builder”语法以使用按值传递的self。对于一个实际的构建器而言,这通常更加符合人体工学原理:

struct MyStruct {
    field1: i32,
    field2: i32,
}

impl MyStruct {
    pub fn field1(self, field1: i32) -> Self {
        MyStruct { field1: field1, ..self }
    }
}

fn foo() -> Option<MyStruct> {
    None
}

fn bar() -> Option<MyStruct> {
    foo().map(|s| s.field1(5))
}

fn main() {
    bar();
}

我也倾向于区分 构建器 (builders) 和简单的 链式方法调用 (chained method invocation)。也就是说,一个构建器应该有一个返回不同类型的 build 方法(或等价物)。构建器通常是短暂的类型,因此按值传递很少会引起问题。


我不确定它是否总是像你说的那样。我见过一些例子,其中构建器本身就是结果,其方法通过 &mut self 接受 self。但也许使用像你建议的不同结构更清晰。 - mbrt

2

编辑:查看Vladimir的答案,更好。

我们应该移除链接吗?

fn bar() -> Option<MyStruct> {
    let x: Option<MyStruct> = foo();
    let x: Option<&mut MyStruct> = x.as_mut();
    let x: Option<&mut MyStruct> = x.map(|s| s.field1(5));
    x
}

因此,去掉废话后,问题是:
如何将 Option<&mut MyStruct> 转换为 Option<MyStruct>
您不能直接这样做,因为借用规则禁止占有仅借用的东西。
您有两个选择:
- 进行克隆 - 回到所有者
让我们来说明一下:
// Requires that MyStruct implement Clone,
// use #[derive(Clone)] to auto-generate the implementation
fn bar_copy() -> Option<MyStruct> {
    foo().as_mut().map(|s| s.field1(5).clone())
}

fn bar_back() -> Option<MyStruct> {
    let mut my_struct = foo();
    my_struct.as_mut().map(|s| s.field1(5));
    my_struct
}

选择你喜欢的方式(我会选择后者以避免复制)。

1
顺便提一下,在你的第二个例子中,我会说使用 if let 更符合惯用语:if let Some(ref mut s) = my_struct { s.field1(5); } - Vladimir Matveev
@VladimirMatveev:我一直在尽量少修改原始代码(因为示例可能是人为制造的),但像你所做的那样删除as_mut确实更好。 - Matthieu M.
好的解释。我尝试了第二个版本,它对我有效。然而,Vladimir的答案只用了一行代码就实现了这个功能。 - mbrt
@brt:是的,这就是为什么我在我的回答顶部添加了免责声明的原因;Vladimir的回答更好 :) - Matthieu M.

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