Чтение изменяемых ссылок имеет другую семантику времени жизни, чем неизменяемые ссылки - PullRequest
0 голосов
/ 25 января 2019

Рассмотрим следующий код, в который помещается ссылка на корневой тип R. Также сохраняется некоторый тип N (avigate), который знает, как разыменовать R для T.

use std::ops::Deref;

struct Wrapper<'r, R, N, T>
where
    N: Fn(&'r R) -> &T,
    T: 'static,
{
    r: &'r R,
    n: N,
}

impl<'r, R, N, T> Deref for Wrapper<'r, R, N, T>
where
    N: Fn(&'r R) -> &T,
    T: 'static,
{
    type Target = T;

    fn deref(&self) -> &T {
        let r: &'r R = self.r;
        let t: &'r T = (self.n)(r);
        t
    }
}

Теперь, если мы изменим наш ссылочный тип r: &'r R, на изменяемый r: &'r mut R,, он больше не будет работать:

use std::ops::Deref;

struct Wrapper<'r, R, N, T>
where
    N: Fn(&'r R) -> &T,
    T: 'static,
{
    r: &'r mut R,
    n: N,
}

impl<'r, R, N, T> Deref for Wrapper<'r, R, N, T>
where
    N: Fn(&'r R) -> &T,
    T: 'static,
{
    type Target = T;

    fn deref(&self) -> &T {
        let r: &'r R = self.r;
        let t: &'r T = (self.n)(r);
        t
    }
}

Ошибка:

error[E0312]: lifetime of reference outlives lifetime of borrowed content...
  --> src/lib.rs:21:24
   |
21 |         let r: &'r R = self.r;
   |                        ^^^^^^
   |
note: ...the reference is valid for the lifetime 'r as defined on the impl at 13:6...
  --> src/lib.rs:13:6
   |
13 | impl<'r, R, N, T> Deref for Wrapper<'r, R, N, T>
   |      ^^
note: ...but the borrowed content is only valid for the anonymous lifetime #1 defined on the method body at 20:5
  --> src/lib.rs:20:5
   |
20 | /     fn deref(&self) -> &T {
21 | |         let r: &'r R = self.r;
22 | |         let t: &'r T = (self.n)(r);
23 | |         t
24 | |     }
   | |_____^

Мы получаем лучшее сообщение об ошибке с nll:

error: lifetime may not live long enough
  --> src/lib.rs:21:16
   |
13 | impl<'r, R, N, T> Deref for Wrapper<'r, R, N, T>
   |      -- lifetime `'r` defined here
...
20 |     fn deref(&self) -> &T {
   |              - let's call the lifetime of this reference `'1`
21 |         let r: &'r R = self.r;
   |                ^^^^^ type annotation requires that `'1` must outlive `'r

Я аннотировал время жизни в deref, чтобы убедиться, что я нахожусь на том же пути, что и компилятор, относительно времени жизни. Сообщение nll особенно интересно, потому что оно говорит, что для выживания требуется &self 'r.

Но это не имеет смысла для меня, поскольку, если мы аннотируем время жизни для deref, оно должно выглядеть так:

fn deref<'1>(&'1 self) -> &'1 T;

А точнее требуется 'r: '1, что неявно дается Wrapper<'r, ...>

Эта интуиция, кажется, имеет место в первом примере, но не во втором с неизменной ссылкой.

Итак, для меня раскрываются два вопроса:

  1. Почему это имеет значение, если self.r неизменен или нет? В любом случае я не могу получить доступ к r изменчиво, поскольку &self является неизменным.
  2. Является ли 1. фундаментальным ограничением или код может быть аннотирован таким образом, чтобы сообщить rustc, что я хочу сделать?

1 Ответ

0 голосов
/ 27 января 2019

Типы черт инвариантны относительно своих общих параметров.

Рассмотрим этот пример:

struct Test<'a, F: Fn(&'a i32)> {
    i: &'a i32,
    f: F,
}

fn main() {
    let i = 1i32;
    let t = Test { i: &i, f: |&_| {} };

    {
        let j = 2i32;
        (t.f)(&j);
    }

    println!("{:?}", t.i);
}

Это даст ошибку:

error[E0597]: `j` does not live long enough
  --> src/main.rs:12:15
   |
12 |         (t.f)(&j);
   |               ^^ borrowed value does not live long enough
13 |     }
   |     - `j` dropped here while still borrowed
14 | 
15 |     println!("{:?}", t.i);
   |                      --- borrow later used here

Как видите, тип Test<'a ... не унифицирован для более короткоговремя жизни с j, потому что Test содержит атрибут impal типа N (статическая отправка).В результате оно будет инвариантно относительно 'a, следовательно, 'a не может быть сокращено.Но j не подходит для 'a, отсюда и ошибка.

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

struct Wrapper<'r, R, N>
where
    N: Fn(&'r R),
{
    r: &'r mut R,
    n: N,
}

impl<'r, R, N> Wrapper<'r, R, N>
where
    N: Fn(&'r R),
{
    fn myderef(&self) {
        (self.n)(self.r)
    }
}

Это будетвыдайте ту же ошибку:

error[E0312]: lifetime of reference outlives lifetime of borrowed content...
  --> src/lib.rs:14:18
   |
14 |         (self.n)(self.r)
   |                  ^^^^^^
   |
note: ...the reference is valid for the lifetime 'r as defined on the impl at 9:6...
  --> src/lib.rs:9:6
   |
9  | impl<'r, R, N> Wrapper<'r, R, N>
   |      ^^
note: ...but the borrowed content is only valid for the anonymous lifetime #1 defined on the method body at 13:5
  --> src/lib.rs:13:5
   |
13 | /     fn myderef(&self) {
14 | |         (self.n)(self.r)
15 | |     }
   | |_____^

Что именно здесь происходит?&self с временами жизни будет типа &'shorter_lifetime Wrapper<'r, R, N>, а не &'shorter_lifetime Wrapper<'shorter_lifetime, R, N>.'r не будет сокращено до 'shorter_lifetime, так как Wrapper будет инвариантным относительно своего общего параметра времени жизни 'r из-за N.

Теперь, когда мы знаем, что именно является типом аргумента &selfесть, давайте посмотрим, что происходит внутри тела myderef().Тип черты N (статическая отправка) вызывается с self.r.Но self.r является изменяемой ссылкой, которая переопределяется при передаче в (self.r)().Так что теперь у вас есть изменяемая ссылка, которая находится за другой ссылкой (self является ссылкой), которая должна жить в 'r (N нужно, чтобы ее входной аргумент имел время жизни 'r согласно определению),в результате &self тоже должен жить для 'r.Но время жизни &self составляет 'shorter_lifetime, отсюда и ошибка.

Другими словами, если у вас есть &'a & 'b mut T (без отношения подтипов между 'a и 'b) в качестве входного аргумента функции, и компилятор позволяет вам перезагружать внутреннюю ссылку и возвращатьэто, то есть нарушение правил заимствования, так как &mut T уже за ссылкой.Внешняя ссылка «владеет» внутренней ссылкой, главным образом потому, что внутренняя ссылка является изменяемой, и функция нуждается в гарантии того, что внешняя ссылка будет оставаться, по крайней мере, до тех пор, пока внутренняя (изменяемая) ссылка перезагружается, в противном случае после вызова функции тамбудет более одного владельца изменяемой ссылки.

Например, следующий код не будет компилироваться:

fn test<'a, 'b> (i:&'a &'b mut i32) -> &'b i32 {
    &**i
}

Но этот будет:

fn test<'a:'b, 'b> (i:&'a &'b mut i32) -> &'b i32 {
    &**i
}

поскольку есть гарантия, что 'a будет жить как минимум столько же, сколько и 'b.

Если внутренняя ссылка неизменна, то первая также скомпилируется, поскольку вы можете иметь несколько неизменных ссылок.Не существует понятия внешней ссылки, «владеющей» внутренней ссылкой.

Чтобы компилировать минимальную версию, мы должны сообщить компилятору, что &self также существует для 'r.Либо это, либо удалите жесткое ограничение 'r на входной аргумент N (время жизни).

В вашем примере deref() не позволит вам указать время жизни для &self, согласно определению Deref.Если вы удалите жесткое ограничение 'r для входного аргумента N, будет скомпилировано

...