fn main() { let mut stack_a = 3; let mut heap_a = Box 3; foo(&mut stack_a); println!("{}",stack_a); let r = foo2(&mut stack_a); // compile error if uncomment next line // println!("{}",stack_a); bar(heap_a); // compile error if uncomment next line // println!("{}",heap_a); } fn foo(x: &mut int){ *x = 5; } fn foo2(x: &mut int) -> &mut int{ *x = 5; x } fn bar(mut x: Box<int>){ *x = 5; }
为什么heap_a移动到函数,但stack_a不是(stack_a仍然可用在println!调用foo后)?
碎片碎片:
`heap_a` moved here because it has type `Box<int>`,which is non-copyable
我假设这在某种程度上取决于生命,因为在“foo2”的情况下,“stack_a”也移动到函数。如果我的假设是真的,如果“foo”,“stack_a”也移动到函数,但是rustc发现,它的生命周期结束于“foo”结尾,并允许在“foo”结束后使用“stack_a”。那么为什么“heap_a”的生命没有结束在“bar”的结尾?
解决方法
与foo(& mut stack_a)的区别在于您不通过值传递stack_a。你只需要“借出”foo stack_a,这样一来,foo能够改变它。什么是获得的是一个借用的指针。当执行从foo回来时,stack_a仍然存在(可能通过foo修改)。你可以将它看作是stack_a返回到自己的堆栈框架,因为foo只是借了一会儿。
似乎混淆你的部分是通过取消注释最后一行
let r = foo2(&mut stack_a); // compile error if uncomment next line // println!("{}",stack_a);
你实际上没有测试stack_a是否被移动。 stack_a还在那里编译器根本不允许您通过其名称访问它,因为您仍然可以借用它引用它:r。这是我们需要的内存安全规则之一:如果我们也允许改变它,只能有一种访问内存位置的方法。在这个例子中,r是一个可以借用的stack_a引用。所以,stack_a仍被认为是可借用的。访问它的唯一方法是通过借用的引用r。
使用一些额外的花括号,我们可以限制借用引用的生命周期r:
let mut stack_a = 3i; { let r = foo2(&mut stack_a); // println!("{}",stack_a); WOULD BE AN ERROR println!("{}",*r); // Fine! } // <-- borrowing ends here,r ceases to exist // No aliasing anymore => we're allowed to use the name stack_a again println!("{}",stack_a);
关闭括号后,只有一种访问内存位置的方法:名称stack_a。这就是为什么编译器让我们在println!中使用它。
现在你可能会想,编译器如何知道r实际上是指stack_a?是否分析了foo2的实现?不,没有必要。 foo2的功能签名就足以得出这个结论。它的
fn foo2(x: &mut int) -> &mut int
这实际上是短的
fn foo2<'a>(x: &'a mut int) -> &'a mut int
根据所谓的“终身检验规则”。这个签名的含义是:foo2是一个函数,它使用一个借用的指针到一些int,并返回一个借用的指针,这个int指针是同一个int(或至少是原始int的“part”),因为同一个生命周期参数用于返回类型。只要你坚持这个返回值(r),编译器就认为stack_a可以借用。
如果您有兴趣为什么我们需要同时禁止别名和(潜在)突变发生。一些记忆位置,查看Niko’s great talk。