Понимание ошибки «Заемные средства живут недостаточно долго» - PullRequest
2 голосов
/ 27 марта 2020

Я пытаюсь понять, почему я получаю сообщение об ошибке при попытке скомпилировать следующий код

trait Foo<'a> {
    fn foo(&'a self) -> RefHolder<'a>;
}

struct SomeType;
impl<'a> Foo<'a> for SomeType {
    fn foo(&'a self) -> RefHolder<'a> {
        RefHolder(self)
    }
}

struct RefHolder<'a>(&'a (dyn Foo<'a>));
struct Container<'a> {
    pub objs: Vec<Box<dyn Foo<'a>>>,
}

fn main() {
    let mut c = Container { objs: Vec::new() };
    c.objs.push(Box::from(SomeType {}));

    let o = &c.objs[0].as_ref();
    let x = o.foo();
}

Я получаю сообщение об ошибке

error[E0597]: `c.objs` does not live long enough
  --> src/main.rs:21:14
   |
21 |     let o = &c.objs[0].as_ref();
   |              ^^^^^^ borrowed value does not live long enough
22 |     let x = o.foo();
23 | }
   | -
   | |
   | `c.objs` dropped here while still borrowed
   | borrow might be used here, when `c` is dropped and runs the destructor for type `Container<'_>`

Я не понимаю, почему c.objs по-прежнему заимствовано в конце main. Насколько я понимаю, сначала будет удалено x, а затем o, что означает, что в этот момент не должно существовать никаких ссылок на c, что позволит окончательно удалить c без проблем.

Ответы [ 2 ]

1 голос
/ 27 марта 2020

Это не полный ответ, потому что я не до конца понимаю источник ошибки, но, по сути, теперь я считаю, что компилятор загадочно говорил вам, что у него недостаточно информации для разрешения времени жизни.

Я считаю, что часто бывает полезно удалить все жизни и добавить их по частям только по мере необходимости, чтобы лучше понять, что происходит, поэтому давайте сделаем это.

Начало:

trait Foo {
    fn foo(&self) -> RefHolder;
}

struct SomeType;

impl Foo for SomeType {
    fn foo(&self) -> RefHolder {
        RefHolder(self)
    }
}

struct RefHolder(&(dyn Foo));

// ...etc

Очевидно, что RefHolder нужен пожизненный параметр из-за содержания заимствования, поэтому добавление их в:

trait Foo {
    fn foo(&self) -> RefHolder<'a>;
}

struct SomeType;

impl Foo for SomeType {
    fn foo(&self) -> RefHolder<'a> {
        RefHolder(self)
    }
}

struct RefHolder<'a>(&'a (dyn Foo));

// ...etc

Теперь foo() нуждается в нем. Обратите внимание, не Foo, просто функция.

trait Foo {
    fn foo<'a>(&self) -> RefHolder<'a>;
}

struct SomeType;

impl Foo for SomeType {
    fn foo<'a>(&self) -> RefHolder<'a> {
        RefHolder(self)
    }
}

struct RefHolder<'a>(&'a (dyn Foo));

// ...etc

Теперь компилятор скажет нам гораздо более прямо, что он не может определить время жизни возвращаемого значения foo в импл. Он скажет, что ему нужно прожить до 'a (из-за подписи), а также до анонимного времени жизни &self (потому что он возвращает self). Но ничего не сказано о том, как эти жизни связаны друг с другом. Итак, мы говорим:

trait Foo {
    fn foo<'a, 's: 'a>(&'s self) -> RefHolder<'a>;
}

struct SomeType;

impl Foo for SomeType {
    fn foo<'a, 's: 'a>(&'s self) -> RefHolder<'a> {
        RefHolder(self)
    }
}

struct RefHolder<'a>(&'a (dyn Foo));

// ...etc

Теперь все счастливы. То, что мы сделали, сказали, что 's должно быть не меньше, чем 'a (в противном случае self может быть уничтожено до того, как RefHolder ссылается на него). Когда мы его называем, проверка чека заимствований имеет место, и с миром все в порядке.

Раньше вы начали добавлять время жизни к черте Foo, что является проигрышной битвой, потому что вы в итоге окажетесь с бесконечным регрессом, так как RefHolder внезапно нужно сказать, как долго его dyn Foo захочет быть заимствованным, а это не то, что вы знаете заранее.

Все еще не совсем уверен, почему это добавляет вплоть до той ошибки, которую вы впервые увидели, но я рад, что хотя бы частично распутал это.

1 голос
/ 27 марта 2020

Когда компилятор говорит, что заимствованное значение не живет достаточно долго , что на самом деле означает, что время жизни этого значения используется вне его диапазона. Компилятор формулирует это так, потому что это самая обычная причина нарушения срока жизни, но ваш код оказывается более сложным из-за того, как определяется срок жизни.

Ключевая строка в вашем коде:

let x = o.foo();

С ним код компилируется нормально. На самом деле это эквивалентно этому

let o : &dyn Foo = c.objs[0].as_ref();
Foo::foo(o);

(дополнительный & не нужен, но это не важно).

Теперь, каково время жизни этого o ссылка? Ну, поскольку он инициализируется из Box::as_ref() и определяется как (время жизни без учета):

fn as_ref<'s>(&'s self) -> &'s T

это то же время жизни, что и у самого Box, что оно взято из вектор, используя черту Index ... так что в конечном итоге это будет время жизни c.objs.

Теперь, из-за способа определения вашей черты:

fn foo(&'a self) -> RefHolder<'a>

возвращенная черта имеет ту же самую жизнь. И поскольку каждый generi c в вашем коде использует одно и то же время жизни, то же самое относится и время жизни Container<'a>.

То есть конкретное время жизни c: Container<'?> - это время жизни одного из его членов. Это аналогично самоссылочной структуре, и это не разрешено.

Ваш код можно легко скомпилировать, просто удалив все жизненные универсальные шаблоны, кроме тех, которые действительно требуются:

trait Foo {
    fn foo<'a>(&'a self) -> RefHolder<'a>;
}

struct SomeType;
impl Foo for SomeType {
    fn foo<'a>(&'a self) -> RefHolder<'a> {
        RefHolder(self)
    }
}

struct RefHolder<'a>(&'a (dyn Foo));
struct Container {
    pub objs: Vec<Box<dyn Foo>>,
}

fn main() {
    let mut c = Container { objs: Vec::new() };
    c.objs.push(Box::from(SomeType {}));

    let o : &dyn Foo = c.objs[0].as_ref();
    let x = o.foo();
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...