为何将闭包移入另一个闭包后仍能返回原闭包的Rust代码可正常编译?
为何将闭包移入另一个闭包后仍能返回原闭包的Rust代码可正常编译?
嘿,这个问题问得特别到位——乍一看确实像是违背了Rust最基础的所有权规则,但其实是闭包的一个特殊特性在暗中起作用,咱们一点点拆解清楚:
首先先把你的代码贴出来方便对照:
fn test() -> (impl FnMut(&mut u8), impl FnMut(&mut u8)) { let a = |v: &mut u8| {*v = 0}; let b = move |v: &mut u8| { a(v); println!("{}", v) }; (a, b) } fn main() { let mut val: u8 = 10; let (mut a, mut b) = test(); b(&mut val); val = 10; a(&mut val); assert!(val == 0); }
核心原因:无捕获闭包会被隐式转为可复制的函数指针
你定义的闭包a有个关键特点:它没有捕获任何外部环境变量——整个闭包逻辑只用到了传入的参数v,没有引用或移动任何外部变量。
在Rust中,这类无捕获闭包会被自动强制转换为对应的函数指针类型(这里就是fn(&mut u8))。而函数指针是实现了Copy trait的类型,这意味着当你用move关键字把a移入闭包b时,实际上只是复制了一份函数指针到b的捕获环境里,原变量a的所有权根本没被真正移走!
这就是为什么你之后还能正常返回a,并且在main里调用它——你返回的是原本的那个函数指针副本,和b里持有的是指向同一逻辑的指针,调用时都会执行把传入的u8置0的操作。
验证这个结论的小实验
如果我们给闭包a加上捕获变量,让它不再是无捕获闭包,比如:
fn test() -> (impl FnMut(&mut u8), impl FnMut(&mut u8)) { let x = 0u8; let a = |v: &mut u8| {*v = x}; // 现在a捕获了外部变量x let b = move |v: &mut u8| { a(v); println!("{}", v) }; (a, b) // 这里编译器会报错:a已经被移动到b中,无法再次使用 }
这时候编译器就会抛出我们熟悉的“value moved here”错误,完全符合常规的所有权规则——因为此时a是一个捕获了环境的闭包,无法被转为函数指针,move操作真的转移了它的所有权,自然不能再返回它了。
额外补充
Rust设计这个特性的目的,是让这类简单的无捕获闭包能像普通函数一样灵活传递、复制,毕竟它们的行为和普通函数完全一致,没必要用严格的所有权规则限制它们。
内容来源于stack exchange
相关产品推荐
相关产品推荐

