【问题标题】:Modify struct field inside of Option修改选项内的结构字段
【发布时间】:2015-08-28 12:44:17
【问题描述】:

我在更改选项内部结构的字段时遇到问题。这是代码:

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&lt;MyStruct&gt; 的函数中获取Option&lt;MyStruct&gt;,更改该结构的字段(如果结果不是None)并返回结果Option&lt;MyStruct&gt; .

结构体实现了构建器模式,所以我用过。

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

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) -&gt; Self,但它们都不起作用。

我怎样才能使这段代码工作?

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

【问题讨论】:

    标签: rust


    【解决方案1】:

    根本没有理由使用as_mut()。因为需要按值返回MyStruct,所以可以只在选项上map

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

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

    【讨论】:

    • 这对我不起作用。它一直说和以前一样的错误:类型不匹配。也许是field1()的签名?
    • 您需要|mut s| { s.field1(5); s }。该方法修改了对象,但它的返回值 &mut 对于按值返回是无用的。
    • 哦,对不起。你是对的。我不知道为什么我使用类似构建器的语法。我已经确定了答案。
    • @VladimirMatveev 好的,现在它可以工作了,而且只是一个很小的变化。为我解决了。
    【解决方案2】:

    TBH,我会按照明显的方式来做:

    fn bar() -> Option<MyStruct> {
        let mut val = foo();
        if let Some(ref mut s) = val {
            s.field1(5);
        }
        val
    }
    

    它比使用map 更长,但我发现使用map 进行突变会让人不舒服,而且我选择的显式突变也会产生更好的组装。

    【讨论】:

    • 是的,这很清楚有效,但是我想知道为什么map 没有像我使用的那样工作。
    【解决方案3】:

    编辑: 检查弗拉基米尔的答案,更好。

    让我们删除链接,好吗?

    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&lt;&amp;mut MyStruct&gt; 转换为Option&lt;MyStruct&gt;

    你不能按原样,借用规则禁止对你只借的东西拥有所有权。

    你有两种选择:

    • 进行克隆
    • 回到主人那里

    让我们举例说明:

    // 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
    }
    

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

    【讨论】:

    • 顺便说一句,在您的第二个示例中,我会说使用 if let: if let Some(ref mut s) = my_struct { s.field1(5); } 更为惯用。
    • @VladimirMatveev:我打算对 OP 代码进行最少的修改(因为示例可能是人为的),但是像您所做的那样删除 as_mut 会更好。
    • 很好的解释。我已经尝试了第二个版本,它对我有用。然而,弗拉基米尔的回答是在一行中做到这一点的。
    • @brt:是的,这就是我在答案顶部添加免责声明的原因;弗拉基米尔的回答更好:)
    【解决方案4】:

    由于其他人提出了更广泛的建议,我要指出,您可以将“构建器”语法更改为按值使用 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 和普通的链式方法调用。也就是说,构建器应该有一个build 方法(或等效方法),它实际上返回不同的类型。构建器通常是短命的类型,因此按值传递很少会导致问题。

    【讨论】:

    • 不知道是不是一直如你所说。我看到了构建器是结果本身的示例,其方法采用self by &amp;mut。但也许像你建议的那样使用不同的结构来构建更清晰。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-31
    • 1970-01-01
    • 2017-03-04
    • 1970-01-01
    相关资源
    最近更新 更多