Кто заимствовал переменную?
Я сражаюсь с заемщиком. У меня есть два похожих фрагмента кода, один из которых работает, как я ожидаю, а другой нет.
Тот, который работает так, как я ожидаю:
mod case1 {
struct Foo {}
struct Bar1 {
x: Foo,
}
impl Bar1 {
fn f<'a>(&'a mut self) -> &'a Foo {
&self.x
}
}
// only for example
fn f1() {
let mut bar = Bar1 { x: Foo {} };
let y = bar.f(); // (1) 'bar' is borrowed by 'y'
let z = bar.f(); // error (as expected) : cannot borrow `bar` as mutable more
// than once at a time [E0499]
}
fn f2() {
let mut bar = Bar1 { x: Foo {} };
bar.f(); // (2) 'bar' is not borrowed after the call
let z = bar.f(); // ok (as expected)
}
}
Тот, который не выполняет:
mod case2 {
struct Foo {}
struct Bar2<'b> {
x: &'b Foo,
}
impl<'b> Bar2<'b> {
fn f(&'b mut self) -> &'b Foo {
self.x
}
}
fn f4() {
let foo = Foo {};
let mut bar2 = Bar2 { x: &foo };
bar2.f(); // (3) 'bar2' is borrowed as mutable, but who borrowed it?
let z = bar2.f(); // error: cannot borrow `bar2` as mutable more than once at a time [E0499]
}
}
Я надеялся, что дважды позвоню Bar2::f
, не раздражая компилятор, как в случае 1.
Вопрос находится в комментарии (3): кто заимствовал bar2
, тогда как нет никакой аффектации?
Вот что я понимаю:
-
В случае 1, f2
вызов: параметр lifetime 'a
является одним из принимающего значения &Foo
, поэтому это время жизни пуст, если нет аффектации, а bar
не заимствовано после вызова Bar1::f
;
-
В случае 2, bar2
занимает foo
(как неизменяемое), поэтому параметр lifetime 'b
в bar2
struct - это ресурс foo
, который заканчивается в конце f4
корпус. Вызов Bar2::f
занимает bar2
для этого времени жизни, а именно до конца f4
.
Но вопрос по-прежнему: кто заимствовал bar2
? Может ли это быть Bar2::f
? Как Bar2::f
будет удерживать заемную собственность после разговора? Что мне здесь не хватает?
Я использую Rust 1.14.0-nightly (86affcdf6 2016-09-28) на x86_64-pc-windows-msvc.
Ответы
Ответ 1
А... ты сам сам заимствовал себя.
Проблема зависит от того, что у вас одинаковое время жизни ('b
), используемое как для времени жизни Foo
, так и для времени жизни Bar
. Затем компилятор покорно объединяет эти времена жизни, и вы попадаете в странную ситуацию, когда внезапное время жизни заимствования, которое должно было закончиться в конце инструкции, заканчивается после того, как значение должно выйти из области видимости.
Как правило: всегда используйте новое время жизни для self
. Все остальное странно.
Интересно отметить, что этот шаблон действительно может быть полезен (хотя более вероятно с неизменным заимствованием): он позволяет привязать значение к кадру стека, предотвращая любое перемещение после вызова функции, которое (иногда) полезно для представления заимствования, который не очень хорошо моделируется Rust (например, передавая указатель на значение FFI).
Ответ 2
В случае №2 у вас есть следующее:
impl<'b> Bar2<'b> {
fn f(&'b mut self) -> &'b Foo {
self.x
}
}
Чтобы выделить: &'b mut self
и &'b Foo
имеют одинаковое время жизни.
Это говорит о том, что ссылка на self
и возвращаемая ссылка на экземпляр Foo
имеют одинаковое время жизни. Посмотрев на сайт вызова, у вас есть следующее:
let foo = Foo {};
let mut bar2 = Bar2 { x: &foo };
Итак, компилятор выводит, что оба Foo
и bar2
имеют одинаковое время жизни. Время жизни Foo
является областью действия функции f4
, поэтому переменная ссылка на bar2
разделяет это.
Один из способов исправить это - удалить явное время жизни в ссылке self
:
fn f(&mut self) -> &'b Foo
Это компилируется, и компилятор правильно понимает, что ссылка на bar2
и ссылка на Foo
имеют разные сроки жизни.
Игровая площадка: https://play.rust-lang.org/?gist=caf262dd628cf14cc2884a3af842276a&version=stable&backtrace=0
TL;DR: Да, имея один и тот же спецификатор времени жизни на самоопределении, и возвращаемая ссылка означает, что весь объем f4
содержит изменяемый заимствование bar2
.
Ответ 3
Я положил тело f4()
в main()
и реализовал Drop
для Bar2
, чтобы узнать, когда он отбрасывается (т.е. выходит за рамки):
impl<'b> Drop for Bar2<'b> {
fn drop(&mut self) { println!("dropping Bar2!"); }
}
И результат:
error: `bar2` does not live long enough
--> <anon>:24:5
|
24 | bar2.f();
| ^^^^ does not live long enough
25 | }
| - borrowed value dropped before borrower
|
= note: values in a scope are dropped in the opposite order they are created
Что-то подозрительное; давайте рассмотрим его подробно, с вспомогательными областями:
fn main() {
{
let foo = Foo {}; // foo scope begins
{
let mut bar2 = Bar2 { x: &foo }; // bar2 scope begins; bar2 borrows foo
bar2.f();
} // bar2 should be dropped here, but it has the same lifetime as foo, which is still live
} // foo is dropped (its scope ends)
}
Мне кажется, что здесь есть утечка, а Bar2
никогда не отбрасывается (и, следовательно, Drop
для него невозможно реализовать). Вот почему вы не можете заимствовать его.