有没有一种方法可以使用Option::and_then缩短非Copy类型的匹配表达式?

11

Option::and_then函数可以简化以下代码:

let foo = Some(1);
let bar = match foo {
    Some(i) => Some(i + 1),
    None => None,
};
println!("Foo: {:?}", foo);

转化为这个:

let foo = Some(1);
let bar = foo.and_then(|i| Some(i + 1));
println!("Foo: {:?}", foo);

如果我用String尝试相同的操作,它不会编译:

let foo = Some("bla".to_string());
let bar = foo.and_then(|ref f| Some(f.clone()));
println!("Foo: {:?}", foo);

error[E0382]: use of moved value: `foo`
 --> src/main.rs:4:27
  |
3 |     let bar = foo.and_then(|ref f| Some(f.clone()));
  |               --- value moved here
4 |     println!("Foo: {:?}", foo);
  |                           ^^^ value used here after move
  |
  = note: move occurs because `foo` has type `std::option::Option<std::string::String>`, which does not implement the `Copy` trait

然而,相应的match表达式是有效的:

let foo = Some("bla".to_string());
let bar = match foo {
    Some(ref f) => Some(f.clone()),
    None => None,
};
println!("Foo: {:?}", foo);

有没有一种方法可以像我的第一个整数示例那样缩短此匹配表达式?

在 playground 上查看代码

  • 在这个最简示例中,我可以使用 map,但在我真正的代码中,我正在调用另一个返回 Option 的函数,所以我确实需要使用 and_then。只是我不想用一个额外的函数来复杂化没有影响到问题的示例。

  • 我真的需要在之后使用 foo,否则就不会有任何问题(实际上,foo 被一个闭包所捕获,我需要多次使用它,而且我的天啊!我费了好大劲才找到为什么编译器一直拒绝我的代码!错误信息“the trait FnMut... is not implemented for the type [closure@...]”并没有给出太多关于为什么不行的提示)。

  • 我在示例中使用了 clone,因为我想要一个使用字符串的简单操作。在真正的代码中,foo 不是一个字符串(而是一个 Regex),我没有在闭包中克隆它(我正在应用它到一个字符串上并处理结果)。此外,这段代码将被调用很多次,因此避免不必要的分配和复制非常重要。

4个回答

15

解释

首先:你实际上想要使用的方法是map,因为你只想改变内部值。如果你在闭包中创建另一个Option,那么and_then是有用的。

回答你的问题:你不能再访问foo是正确的。如果你看一下函数声明...

fn and_then<U, F: FnOnce(T) -> Option<U>>(self, f: F) -> Option<U>
//                                        ^^^^

...你会发现第一个参数是self。这意味着该方法消耗了self(获取所有权),因此foo被移动到方法中,不再可用。

解决方案

如果之后只需要使用bar(通常情况下),则应该直接打印bar。如果确实需要使用foo,也可以这样做:

let bar = foo.as_ref().map(|s| s.clone());

as_ref创建一个新的Option,仅持有对原始内部变量的引用。引用是Copy类型,因此Option可以安全地通过map消费。


5

您想要使用Option::as_ref

fn main() {
    let foo = Some("bla".to_string());
    let bar = foo.as_ref().and_then(|f| Some(f.clone()));
    println!("Foo: {:?}", foo);
}

0
你可以克隆foo,然后在结果上调用and_then
let bar = foo.clone().and_then (|f| Some (f));

1
然而,克隆并不一定便宜。如果可能的话,在这里应该优先考虑其他方法... - Lukas Kalbertodt
@LukasKalbertodt: 我意识到通过这种方法,我可以在 lambda 中去掉额外的克隆。那么,在这样做之后,我们不是以使用 as_ref 方法时相同数量的克隆结束了吗?也就是一个? - Benjamin Lindley
绝对正确。我只是认为lambda内部的clone只是一个例子,OP可能想将其更改为更有意义的内容。目前你的答案中的and_then没有任何作用... :/ - Lukas Kalbertodt
@LukasKalbertodt:嗯,我也想到了。为了做类似于他在整数示例中所做的事情,他可能会执行类似于f +“one”或字符串上的其他修改操作。无论哪种方式,你都需要在某个时候克隆该字符串。我只是提前做了这件事。 - Benjamin Lindley

0

Option 实现了 Clone,其工作方式与您预期的相同。

let foo = Some("bla".to_string());
let bar = foo.clone();

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