通过引用将值传递给函数和通过 Box 传递值有什么区别? [英] What is the difference between passing a value to a function by reference and passing it by Box?

查看:49
本文介绍了通过引用将值传递给函数和通过 Box 传递值有什么区别?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

通过引用将值传递给函数和按框"传递有什么区别:

What is the difference between passing a value to a function by reference and passing it "by Box":

fn main() {
    let mut stack_a = 3;
    let mut heap_a = Box::new(3);

    foo(&mut stack_a);
    println!("{}", stack_a);

    let r = foo2(&mut stack_a);
    // compile error if the next line is uncommented
    // println!("{}", stack_a);

    bar(heap_a);
    // compile error if the next line is uncommented
    // println!("{}", heap_a);
}

fn foo(x: &mut i32) {
    *x = 5;
}

fn foo2(x: &mut i32) -> &mut i32 {
    *x = 5;
    x
}

fn bar(mut x: Box<i32>) {
    *x = 5;
}

为什么 heap_a 移动到函数中,而 stack_a 没有(stack_aprintln 中仍然可用!foo() 调用之后的 code> 语句)?

Why is heap_a moved into the function, but stack_a is not (stack_a is still available in the println! statement after the foo() call)?

取消注释时出错println!("{}", stack_a);:

error[E0502]: cannot borrow `stack_a` as immutable because it is also borrowed as mutable
  --> src/main.rs:10:20
   |
8  |     let r = foo2(&mut stack_a);
   |                       ------- mutable borrow occurs here
9  |     // compile error if the next line is uncommented
10 |     println!("{}", stack_a);
   |                    ^^^^^^^ immutable borrow occurs here
...
15 | }
   | - mutable borrow ends here

我认为这个错误可以通过引用生命周期来解释.在foo的情况下,stack_a(在main函数中)被移动到函数foo,但是编译器发现函数 foo, x: &mut i32 的参数的生命周期在 foo 的末尾结束.因此,它允许我们在 foo 返回后在 main 函数中使用变量 stack_a.在foo2的情况下,stack_a也移动到函数中,但我们也返回它.

I think this error can be explained by referring to lifetimes. In the case of foo, stack_a (in the main function) is moved to function foo, but the compiler finds that the lifetime of the argument of the function foo, x: &mut i32, ends at end of foo. Hence, it lets us use the variable stack_a in the main function after foo returns. In the case of foo2, stack_a is also moved to the function, but we also return it.

为什么 heap_a 的生命周期不会在 bar 的末尾结束?

Why doesn't the lifetime of heap_a end at end of bar?

推荐答案

值传递始终是副本(如果涉及的类型是平凡的")或移动(如果不是).Box 是不可复制的,因为它(或至少它的一个数据成员)实现了 Drop.这通常用于某种清理"代码.Box 是一个拥有指针".它是它所指向内容的唯一所有者,这就是为什么它感到有责任"在其 drop 函数中释放 i32 的内存.想象一下,如果您复制一个 Box 会发生什么:现在,您将有两个 Box 实例指向相同的内存位置.这会很糟糕,因为这会导致双重释放错误.这就是为什么 bar(heap_a) 移动 Box 实例到 bar() 中的原因.这样,堆分配的 i32 始终只有一个所有者.这使得管理内存变得非常简单:无论谁拥有它,最终都会释放它.

Pass-by-value is always either a copy (if the type involved is "trivial") or a move (if not). Box<i32> is not copyable because it (or at least one of its data members) implements Drop. This is typically done for some kind of "clean up" code. A Box<i32> is an "owning pointer". It is the sole owner of what it points to and that's why it "feels responsible" to free the i32's memory in its drop function. Imagine what would happen if you copied a Box<i32>: Now, you would have two Box<i32> instances pointing to the same memory location. This would be bad because this would lead to a double-free error. That's why bar(heap_a) moves the Box<i32> instance into bar(). This way, there is always no more than a single owner of the heap-allocated i32. And this makes managing the memory pretty simple: Whoever owns it, frees it eventually.

foo(&mut stack_a) 的不同之处在于您不按值传递 stack_a.你只是以一种 foo() 能够改变它的方式借出"foo() stack_a.foo() 得到的是一个借用 指针.当执行从 foo() 返回时,stack_a 仍然存在(并且可能通过 foo() 修改).您可以将其视为 stack_a 返回到其拥有的堆栈帧,因为 foo() 只是暂时借用了它.

The difference to foo(&mut stack_a) is that you don't pass stack_a by value. You just "lend" foo() stack_a in a way that foo() is able to mutate it. What foo() gets is a borrowed pointer. When execution comes back from foo(), stack_a is still there (and possibly modified via foo()). You can think of it as stack_a returned to its owning stack frame because foo() just borrowed it only for a while.

让您感到困惑的部分是取消注释最后一行

The part that appears to confuse you is that by uncommenting the last line of

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.

you don't actually test whether stack_a as been moved. stack_a is still there. The compiler simply does not allow you to access it via its name because you still have a mutably borrowed reference to it: r. This is one of the rules we need for memory safety: There can only be one way of accessing a memory location if we're also allowed to alter it. In this example r is a mutably borrowed reference to stack_a. So, stack_a is still considered mutably borrowed. The only way of accessing it is via the borrowed reference r.

通过一些额外的花括号,我们可以限制借用引用的生命周期r:

With some additional curly braces we can limit the lifetime of that borrowed reference r:

let mut stack_a = 3;
{
   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! 中使用它的原因.

After the closing brace there is again only one way of accessing the memory location: the name stack_a. That's why the compiler lets us use it in println!.

现在你可能想知道,编译器怎么知道r实际上是指stack_a?它是否为此分析了 foo2 的实现?不,没有必要.foo2 的函数签名足以得出这个结论.这是

Now you may wonder, how does the compiler know that r actually refers to stack_a? Does it analyze the implementation of foo2 for that? No. There is no need. The function signature of foo2 is sufficient in reaching this conclusion. It's

fn foo2(x: &mut i32) -> &mut i32

实际上是

fn foo2<'a>(x: &'a mut i32) -> &'a mut i32

根据所谓的终生省略规则".这个签名的含义是: foo2() 是一个函数,它接受一个指向某个 i32 的借用指针,并返回一个指向 i32 的借用指针这是相同的 i32(或至少是原始 i32 的一部分"),因为相同的生命周期参数用于返回类型.只要您坚持该返回值 (r),编译器就会认为 stack_a 是可变借用的.

according to the so-called "lifetime elision rules". The meaning of this signature is: foo2() is a function that takes a borrowed pointer to some i32 and returns a borrowed pointer to an i32 which is the same i32 (or at least a "part" of the original i32) because the the same lifetime parameter is used for the return type. As long as you hold on to that return value (r) the compiler considers stack_a mutably borrowed.

如果您对为什么我们需要禁止同时发生别名和(潜在的)突变感兴趣的话.一些内存位置,查看 Niko 的精彩演讲.

If you're interested in why we need to disallow aliasing and (potential) mutation happening at the same time w.r.t. some memory location, check out Niko's great talk.

这篇关于通过引用将值传递给函数和通过 Box 传递值有什么区别?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆