Rust Option::map_or 的默认值也在闭包中使用,编译报 E0382
Option::map_or 的参数提前求值,同一向量不能既作为默认值移动,又由闭包消费。用 match 在选中分支内转移所有权,保留分支行为。
默认分支和 Some 分支不会同时执行,但把同一个向量放进 map_or 的默认参数和闭包,仍然无法通过编译:
fn finish(flag: Option<()>, items: Vec<i32>) -> Vec<i32> {
flag.map_or(items, |_| {
println!("{:?}", items);
items
})
}
诊断通常包含 use of moved value 与 E0382,并指出第一个参数已经移动了 items。这里的冲突发生在构造方法调用参数时,不是在运行时真的执行了两条分支。本文针对标准 Option::map_or 和拥有所有权的 Vec<i32>,使用稳定 Rust 语法。
调用参数先构造,分支后选择
map_or 接收一个现成的默认结果,以及处理 Some 值的闭包。它的参数会提前求值:第一个参数把向量移动进去,闭包又需要取得同一个向量并返回它。
向量没有实现 Copy,不能把同一份所有权交给这两个参数。即使运行时只选择其中一个结果,参数在进入方法前也必须是合法构造的。
mut 只影响是否允许修改绑定或内容,不能让已经移动的值重新可用。诊断提示 clone 是一种可能改法,是否采用取决于结果是否需要独立副本。
用 match 在选中的分支里移动
当两条分支都需要消费同一个值时,可以直接写出控制流:
fn finish(flag: Option<()>, items: Vec<i32>) -> Vec<i32> {
match flag {
Some(()) => {
println!("{:?}", items);
items
}
None => items,
}
}
fn main() {
let first = finish(Some(()), vec![2, 4]);
let second = finish(None, vec![6, 8]);
println!("{:?} {:?}", first, second);
}
match 只执行匹配的分支,向量在该分支内作为结果移动出去。上例的预期行为是 Some 分支先打印自己的向量,两次调用都返回各自收到的元素。
这个改法保留了函数消费输入向量、返回向量的接口。如果调用方还要使用传入的原变量,依然需要重新接收返回值,或另行设计借用接口;修复当前报错不会改变调用边界的所有权转移。
两条分支只差一个动作时,可以先判断再返回
如果业务确实只是“有标记时做一个动作,最后始终返回原向量”,代码还可以缩短:
fn finish(flag: Option<()>, items: Vec<i32>) -> Vec<i32> {
if flag.is_some() {
println!("{:?}", items);
}
items
}
只有两条分支最终返回相同对象时才适合这样合并。若 Some 中还需要使用标记里的值,或两条路径有不同转换,应保留明确的分支结构。
map_or_else 用闭包延迟计算默认结果,适合默认值本来就需要按需生成的情况。但把同一个拥有所有权的值同时交给两个需要消费它的闭包,并不会自动解除所有权冲突。不能把方法名换掉就认为修复完成。
验证时分别覆盖 Some 和 None,确认元素、顺序和分支内动作符合原意。若原代码依赖默认参数求值产生的副作用,改成按分支执行会改变发生时机,需要显式保留真正需要的动作。
回退时保留函数输入、返回类型及分支行为的约定。不要为消除编译错误引入不需要的共享可变状态,或无条件复制大对象;先把值应该在哪个分支被消费写清楚。


