Вызов функции цепочки против использования промежуточных переменных
Я новичок в 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 |
| |
| |
+--------------+
Так что Руст не допустит этого