Почему я могу использовать изменяемую переменную так, чтобы ее время жизни перекрывалось неизменной ссылкой, но я не могу использовать изменяемую ссылку таким же образом? - PullRequest
1 голос
/ 10 марта 2020

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

это компилируется:

let mut s = String::from("hello"); // some warning about s not needing to be mutable
let r = &s;
println!("{}, {}", r, s);

, но это не так:

let mut s = String::from("hello");
let r_mut = &mut s; // mutable borrow here
let r = &s; // immutable borrow here
println!("{}, and {}", r, r_mut); // mutable borrow used here, error

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

1 Ответ

2 голосов
/ 10 марта 2020

Давайте сначала немного исправим терминологию.

Привязки переменных не имеют времени жизни. Они имеют область действия . Когда привязка переменной выходит из области видимости, объект, к которому она привязывается, будет удален. Это поведение не контролируется заемщиком. Области являются лексическими, и их легко увидеть, глядя на исходный код.

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

Когда вы создаете заем для переменной, переменная помечается как заимствованная средством проверки заимствования. В течение продолжительности жизни заимствования, как следует из средства проверки заимствований, вы не можете изменять или перемещать заимствованную переменную независимо от того, объявили ли вы привязку переменной как изменчивую или нет. Если вы создали общий заем, вы можете создавать дополнительные общие заимствования. Однако изменяемые заимствования являются исключительными: переменная, которая является заимствованной переменной, не может быть использована каким-либо другим способом в течение срока действия заимствования.

Эти правила гарантируют, что только одна «ручка» для переменной - либо привязка или изменяемый заем - может использоваться для изменения переменной в любой момент времени. Это фундаментальный инвариант Руста. Тот факт, что область привязки изменяемой переменной может перекрываться со временем жизни заимствования, не меняет этого, поскольку вы не можете изменять переменную через ее привязку в течение времени жизни заимствования.

Строго говоря, нет такой вещи как "изменяемая переменная"; есть только привязок изменяемых переменных . Если у вас есть право собственности на объект, вы всегда можете привязать его к нему, чтобы изменить его:

let s = "Hello".to_owned();
let mut s = s;
s.push_str(", world!");

В вашем коде есть еще одна тонкость: println!() - это макрос, а не вызов функции. Он расширяется до некоторого кода, который только заимствует аргументов, которые вы передаете println!(). Так что, хотя кажется, что вы передаете право владения s на println!(), на самом деле это не так. Если вместо этого вы используете макрос, вступающий во владение, код перестает компилироваться:

let mut s = String::from("hello");
let r = &s;
dbg!(r, s);

, что приводит к ошибке

error[E0505]: cannot move out of `s` because it is borrowed
 --> src/main.rs:4:5
  |
3 |     let r = &s;
  |             -- borrow of `s` occurs here
4 |     dbg!(r, s);
  |     ^^^^^^^^^^^
  |     |
  |     move out of `s` occurs here
  |     borrow later used here
  |
  = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)

См. Также:

...