Кто заимствовал переменную?

Я сражаюсь с заемщиком. У меня есть два похожих фрагмента кода, один из которых работает, как я ожидаю, а другой нет.

Тот, который работает так, как я ожидаю:

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 для него невозможно реализовать). Вот почему вы не можете заимствовать его.