В чем разница между передачей значения функции по ссылке и передачей через 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);
// ошибка компиляции, если раскомментировать следующую строку
// println!("{}", stack_a);
bar(heap_a);
// ошибка компиляции, если раскомментировать следующую строку
// 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_a
все еще доступна в операторе println!
после вызова foo()
)?
При попытке раскомментировать строку 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
. Поэтому компилятор позволяет использовать переменную stack_a
в функции main
после возврата из foo
. В случае функции foo2
, stack_a
также передается в функцию, но мы также возвращаем его.
Почему же время жизни heap_a
не заканчивается в конце функции bar
?
1 ответ(ов)
Разница между передачей по ссылке и "по коробке" заключается в том, что в случае передачи по ссылке (то есть "взять взаймы") вызывающая сторона отвечает за освобождение объекта, тогда как в случае передачи по коробке (то есть "передать") вызываемая сторона отвечает за освобождение объекта.
Поэтому Box<T>
полезен для передачи объектов с ответственностью за освобождение, в то время как ссылка полезна для передачи объектов без ответственности за освобождение.
Вот простой пример, который демонстрирует эти идеи:
fn main() {
let mut heap_a = Box::new(3);
foo(&mut *heap_a);
println!("{}", heap_a);
let heap_b = Box::new(3);
bar(heap_b);
// нельзя использовать `heap_b`. `heap_b` был освобожден в конце `bar`
// println!("{}", heap_b);
} // `heap_a` уничтожается здесь
fn foo(x: &mut i32) {
*x = 5;
}
fn bar(mut x: Box<i32>) {
*x = 5;
} // heap_b (теперь `x`) освобождается здесь
В этом примере мы видим, как передача по ссылке позволяет продолжать использовать объект после вызова функции foo
, в то время как передача через Box
в функции bar
приводит к освобождению объекта, и далее его использование становится невозможным.
Почему println! не работает в юнит-тестах Rust?
Можно ли пометить функцию как устаревшую?
Как умножать/делить/складывать/вычитать числа разных типов?
Что означает "Sized не реализован"?
Лучшие практики использования постоянных хеш-карт в Rust