Проблемы с продолжительностью жизни / брать по типу str - PullRequest
4 голосов
/ 01 мая 2020

Почему этот код компилируется?

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

fn main() {
    let x = "eee";
    let &m;
    {
        let y = "tttt";
        m = longest(&x, &y);
    }
    println!("ahahah: {}", m);
}

Для меня должна быть ошибка компиляции из-за продолжительности жизни. Если я пишу тот же код с i64, я получаю ошибку.

fn ooo<'a>(x: &'a i64, y: &'a i64) -> &'a i64 {
    if x > y {
        x
    } else {
        y
    }
}

fn main() {
    let x = 3;
    let &m;
    {
        let y = 5;
        m = ooo(&x, &y);
    }
    println!("ahahah: {}", m);
}

Ошибка:

error[E0597]: `y` does not live long enough
   --> src/main.rs:103:25
    |
103 |       m = ooo(&x, &y);
    |                   ^^ borrowed value does not live long enough
104 |   }
    |   - `y` dropped here while still borrowed
105 |   println!("ahahah: {}", m);
    |                          - borrow later used here

Ответы [ 2 ]

6 голосов
/ 01 мая 2020

Есть несколько вещей, которые нам нужно знать, чтобы понять это. Во-первых, это тип строкового литерала. Любой строковый литерал (например, "foo") имеет тип &'static str. Это ссылка на фрагмент строки, но, кроме того, это ссылка stati c. Этот вид ссылки длится на всю длину программы и может быть принудительно приведен к любому другому времени жизни.

Это означает, что в вашем первом фрагменте кода x и y уже являются и ссылками, и имеют тип &'static str. Причина, по которой вызов longest(&x, &y) все еще работает (даже если &x и &y имеют тип &&'static str), вызвана принудительным разыменованием. longest(&x, &y) действительно обесцвечивается как longest(&*x, &*y) для приведите типы в соответствие.

Давайте проанализируем времена жизни в первом фрагменте кода.

fn main() {
    // x: &'static str
    let x = "eee";
    // Using let patterns in a forward declaration doesn't really make sense
    // It's used for things like
    // let (x, y) = fn_that_returns_tuple();
    // or
    // let &x = fn_that_returns_reference();
    // Here, it's the same as just `let m;`.
    let &m;
    {
        // y: &'static str
        let y = "tttt";
        // This is the same as `longest(x, y)` due to autoderef
        // m: &'static str
        m = longest(&x, &y);
    }
    // `m: &static str`, so it's still valid here
    println!("ahahah: {}", m);
}

(детская площадка)

С let &m; возможно, вы имели в виду что-то вроде let m: &str, чтобы вызвать его тип. Я думаю, что на самом деле гарантирует, что время жизни ссылки в m начинается с этой предварительной декларации. Но поскольку m имеет тип &'static str, это не имеет значения.


Теперь давайте рассмотрим вторую версию с i64.

fn main() {
    // x: i64
    // This is a local variable
    // and will be dropped at the end of `main`.
    let x = 3;
    // Again, this doesn't really make sense.
    let &m;
    // If we change it to `let m: &i64`, the error changes,
    // which I'll discuss below.
    {
        // Call the lifetime roughly corresponding to this block `'block`.
        // y: i64
        // This is a local variable,
        // and will be dropped at the end of the block.
        let y = 5;
        // Since `y` is local, the lifetime of the reference here
        // can't be longer than this block.
        // &y: &'block i64
        // m: &'block i64
        m = ooo(&x, &y);
    } // Now the lifetime `'block` is over.
      // So `m` has a lifetime that's over
      // so we get an error here.
    println!("ahahah: {}", m);
}

(детская площадка)

Если мы изменим объявление m на let m: &i64 (что я и имел в виду), ошибка изменится.

error[E0597]: `y` does not live long enough
  --> src/main.rs:26:21
   |
26 |         m = ooo(&x, &y);
   |                     ^^ borrowed value does not live long enough
27 |     } // Now the lifetime `'block` is over.
   |     - `y` dropped here while still borrowed
...
30 |     println!("ahahah: {}", m);
   |                            - borrow later used here

(детская площадка)

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


Поскольку обе эти программы работают с литералами, мы фактически можем сделать компиляцию второй версии. Для этого мы должны воспользоваться предложением stati c. Хорошее резюме того, что это значит, можно найти в объявлении Rust 1.21 (в котором был выпущен этот выпуск) или в этом вопросе .

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

fn ooo<'a>(x: &'a i64, y: &'a i64) -> &'a i64 {
    if x > y {
        x
    } else {
        y
    }
}

fn main() {
    // due to promotion
    // x: &'static i64
    let x = &3;
    let m;
    {
        // due to promotion
        // y: &'static i64
        let y = &5;
        // m: &'static i64
        m = ooo(x, y);
    }
    // So `m`'s lifetime is still active
    println!("ahahah: {}", m);
}

(детская площадка)

4 голосов
/ 01 мая 2020

Ваши примеры не совсем совпадают. Строковый литерал "eee" имеет тип &str, а не str, поэтому соответствующий код с целыми числами выглядит следующим образом:

fn ooo<'a>(x: &'a i64, y: &'a i64) -> &'a i64 {
    if x > y {
        x
    } else {
        y
    }
}

fn main() {
    let x = &3;
    let &m;
    {
        let y = &5;
        m = ooo(&x, &y);
    }
    println!("ahahah: {}", m);
}

И он компилируется.

Причина, по которой это (и пример &str) работает потому, что локальные встроенные литеральные ссылки имеют время жизни 'static. Таким образом, они помещаются в часть данных окончательного двоичного файла c и доступны в памяти в течение всего срока службы программы.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...