Вызов функции цепочки против использования промежуточных переменных

Я новичок в Rust, и мне довольно сложно понять всю концепцию владения / заимствования.... даже после прочтения всех официальных руководств.

Почему следующий код компилируется без проблем?

use std::io;

fn main() {
    let mut input = io::stdin(); 
    let mut lock = input.lock(); 
    let mut lines_iter = lock.lines();

    for line in lines_iter {
        let ok = line.ok();
        let unwrap = ok.unwrap();
        let slice = unwrap.as_slice();

        println!("{}", slice);
    }
}

... но это не так?

use std::io;

fn main() {
    let mut lines_iter = io::stdin().lock().lines();

    for line in lines_iter {
        let slice = line.ok().unwrap().as_slice();
        println!("{}", slice);
    }
}

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

При компиляции второго он кричит на меня с большим

 - error: borrowed value does not live long enough
 - note: reference must be valid for the block at 
 - note:...but borrowed value is only valid for the statement  
 - help: consider using a `let` binding to increase its lifetime

Но, честно говоря, я понятия не имею, что компилятор пытается мне сказать. Я понимаю только то, что у меня проблема на всю жизнь. Но почему?

В чем разница между обоими примерами кода? Почему и как это влияет на срок службы чего?

3 ответа

Решение

Определение промежуточных переменных продлевает время жизни промежуточных значений. Временные значения (такие как io::stdin() а также io::stdin().lock() в io::stdin().lock().lines()) перестают существовать в конце оператора, если они не перемещены (что является случаем io::stdin().lock()).

В let mut lines_iter = io::stdin().lock().lines();:

  • io::stdin() возвращает новый Stdin
  • .lock() возвращает новый StdinLock<'a> (который ссылается на Stdin; ты не видишь <'a> в документации потому что срок жизни был указан в источнике)
  • .lines() возвращает новый Lines<StdinLock<'a>> (который вступает во владение замком).

Параметр времени жизни для возвращаемого типа .lock() указывает, что замок заимствует из Stdin объект. Когда вы одалживаете у какого-то предмета, этот предмет должен жить, по крайней мере, до тех пор, пока одолжит. Однако вы пытаетесь получить переменную, которая длится до конца функции, но заимствует из объекта, который будет удален в конце оператора (так как io::stdin() это временное значение).

Историческая справка: Когда этот вопрос был первоначально задан, .lines() будет брать из замка. Сейчас, .lines() вместо этого становится владельцем замка. Это означает, что сейчас только io::stdin() должен быть связан с переменной; больше нет необходимости связывать результат input.lock() ,

Я просто подумал, что вернусь к этому вопросу, так как некоторые детали сейчас разные. (Если честно, я просто не понимаю этого сам, поэтому я решил покопаться в этом материале и записать свои выводы.)

Начнем с этого кода:

use std::io::{stdin, BufRead};

fn main() {
    for l in stdin().lock().lines() {
        println!("{}", l.unwrap());
    }
}

Вот что должен сказать компилятор:

t.rs:4:14: 4:21 error: borrowed value does not live long enough
t.rs:4     for l in stdin().lock().lines() {
                    ^~~~~~~
t.rs:4:5: 6:6 note: reference must be valid for the destruction scope surrounding statement at 4:4...
t.rs:4     for l in stdin().lock().lines() {
t.rs:5         println!("{}", l.unwrap());
t.rs:6     }
t.rs:4:5: 6:6 note: ...but borrowed value is only valid for the statement at 4:4
t.rs:4     for l in stdin().lock().lines() {
t.rs:5         println!("{}", l.unwrap());
t.rs:6     }
t.rs:4:5: 6:6 help: consider using a `let` binding to increase its lifetime

Давайте попробуем что-нибудь попроще:

fn main() {
    let lock = stdin().lock();
}

Это все еще не работает, и ошибка очень похожа. Тот факт, что это не работает, говорит о том, что проблема заключается в stdin() вызов.

t.rs:4:16: 4:23 error: borrowed value does not live long enough
t.rs:4     let lock = stdin().lock();
                      ^~~~~~~
t.rs:3:11: 5:2 note: reference must be valid for the block at 3:10...
t.rs:3 fn main() {
t.rs:4     let lock = stdin().lock();
t.rs:5 }
t.rs:4:5: 4:31 note: ...but borrowed value is only valid for the statement at 4:4
t.rs:4     let lock = stdin().lock();
           ^~~~~~~~~~~~~~~~~~~~~~~~~~
t.rs:4:5: 4:31 help: consider using a `let` binding to increase its lifetime

Давайте посмотрим на типы. stdin возвращается Stdin, который имеет .lock метод:

fn lock(&self) -> StdinLock

Тип возвращаемого значения метода StdinLock, но если вы посмотрите на его объявление, вы увидите, что он использует параметр времени жизни:

pub struct StdinLock<'a> {
    // some fields omitted
}

Пропущенные поля являются причиной этого параметра, и, изучая источники, мы узнаем, что существует MutexGuard внутри, и срок жизни применяется к типу значения, хранящегося внутри защиты. Но это на самом деле не важно. Дело в том, что есть этот параметр времени жизни, что означает, что было задействовано время жизни и объявление lock Метод на самом деле это:

fn lock<'a>(&'a self) -> StdinLock<'a>  /* Self = Stdin */

Так. Наш местный lock переменная имеет тип StdinLock<'a>, это 'a параметр типа означает, что есть некоторая ссылка внутри StdinLock это должно быть действительно как минимум 'a, С другой стороны, от того, что lock является локальной переменной нашей функции, мы знаем, что ее диапазон является телом этой функции, и из того факта, что ее тип StdinLock<'a> компилятор заключает, что 'a диапазон, соответствующий телу функции.

Именно в этот момент мы понимаем, что для вызова .lock() быть действительным self аргумент, который передается в него, должен быть живым для всего тела функции, так как типы говорят нам, что значение, возвращаемое .lock() держит некоторые ссылки на его части. Но он будет уничтожен сразу после завершения оператора, если мы явно не используем let чтобы помочь ему жить дольше.

В итоге мы имеем:

use std::io::{stdin, BufRead};

fn main() {
    let stdin = stdin();
    for l in stdin.lock().lines() {
        println!("{}", l.unwrap());
    }
}

что происходит с работой.

Вот и все, как всегда, все сводится к вопросам собственности. Значение, возвращаемое из .lock() не принимает на себя ответственность за то, что он призван (результат stdin() в нашем случае), но он сохраняет ссылки на него. Это означает, что должен быть кто-то, кто берет на себя ответственность за сохранение результата stdin() назови живым и уничтожь его в какой-то момент, и что кто-то должен быть тобой (и мной), так как других вариантов нет;).

С другой стороны, тип .lines() это:

fn lines(self) -> Lines<Self>  /* Self = StdinLock */

Как вы можете видеть это потребляет self поэтому нам не нужно явно связывать результат .lock(), как значение, возвращаемое из .lines() берет на себя владение замком, поэтому берет на себя ответственность за то, чтобы он оставался в живых столько, сколько ему нужно, а затем разрушил его

                                             XXXXXXXXXXX
                                      XXXXXXXX          XXXXXXX
                                     XX Gets destroyed after  X
                                     X  end of statement     XX
                                     XX if not binded      XX
                             +-----+  XXXXXX      XXXXXXXXXX
                             |             XXXXXXXX
                             v

+-------------+       +-------------+        +----------------+
|  .lock()    |       |  io::stdin()|        |                |
|             +-------->            +-------->   Global       |
|   Lock      |       |StdinReader           |Stdin Buffer    |
|             |       |             |        |                |
|             |       |             |        |                |
+------^------+       +-------------+        +----------------+
       |
       |
       |
       |
+------+-------+
|   .lines()   |
|              |
|  Iterator    |
|              |
|              |
+--------------+

Так что Руст не допустит этого

Другие вопросы по тегам