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

#rust #borrow-checker

Вопрос:

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

 fn main() {
    let mut a = String::new();
    let mut b = String::new();
    let aa = amp;mut a;
    let c = my_fun(aa, amp;mut b);
    let d = amp;mut a;
    println!("{}", c);
}
                
fn my_fun<'b>(x: amp;'b mut String, y: amp;'b mut String) -> amp;'b mut String { y }
 

Насколько я понимаю my_fun , возрождение aa как amp;*aa область применения будет my_fun . Но из-за ограничения срока службы, которое я создал в сигнатуре функции, повторное рождение должно жить по крайней мере столько, сколько amp;mut b существует. Так println что заставьте перерожденца дожить до этого.

Разве это не должно приводить к ошибке использования после бесплатного, потому что анонимное повторное рождение имеет только область действия внутри my_fun ? Вне этой функции эта ссылка не должна быть допустимой.

Но ошибка, которую я получаю, такова:

 error[E0499]: cannot borrow `a` as mutable more than once at a time
 --> src/main.rs:7:13
  |
5 |     let aa= amp;mut a;
  |             ------ first mutable borrow occurs here
6 |     let c = my_fun(aa, amp;mut b);
7 |     let d = amp;mut a;
  |             ^^^^^^ second mutable borrow occurs here
8 |     println!("{}", c);
  |                    - first borrow later used 
 

что имело бы смысл, если бы изменяемая ссылка была просто скопирована, а не переименована внутри функции.

Комментарии:

1. но play.rust-lang.org/… Я не понимаю, что вас блокирует, вы заявляете: «пока возврат my_fun жив, два аргумента должны жить».

2. Я сделал эти границы, чтобы лучше понять, что происходит за возрождениями, а не для того, чтобы заставить это работать. Поэтому мой вопрос , по сути, заключается в том, как возрождение может быть «живым» за пределами своей области, которая здесь является функцией.

3. потому что вы используете c, который заимствует aa после вашего перерождения

Ответ №1:

Насколько я понимаю my_fun , возрождение aa как amp;*aa область применения будет my_fun .

Это не совсем так.

Давайте немного вернемся назад: зачем перерождаться?

Существует фундаментальная разница между amp;T и amp;mut T : amp;T есть Copy , в то amp;mut T время как нет даже Clone . В результате это amp;mut T может быть перемещено только и, следовательно, вызов, такой как:

 let x: amp;mut T = /*...*/;
func(x);
 

Это приведет к x тому, что после звонка он станет непригодным для использования. Тогда обходной путь будет заключаться в введении временного:

 let x: amp;mut T = /*...*/;
let tmp = amp;mut *x;
func(tmp);
 

Это временное явление будет повторно заимствовано у объекта x и будет поглощено func .

И… это повторное заимствование! Компилятор имеет это «временное» создание, встроенное исключительно для эргономики!

Имея это в виду, давайте вернемся к:

Насколько я понимаю my_fun , возрождение aa как amp;*aa область применения будет my_fun .

Продолжительность жизни, как правило, является скорее диапазоном, чем точкой, и здесь это верно.

Срок службы tmp в моем примере выше ограничен 2 способами:

  • Она не может быть больше, чем у x .
  • Это не может быть меньше, чем у func .

Это еще один способ сказать, что это может быть что угодно между этими границами.

Комментарии:

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

Ответ №2:

Я полагаю, что вы слишком много думаете о «возрождении» здесь.

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

Отдельной ссылки на «возрожденное» нет. Заимствование-это ведение бухгалтерского учета внутри компилятора для отслеживания продолжительности жизни. Нет никакого let x = amp;*aa шага, который на самом деле происходит или даже специально подразумевается. Это не похоже на подсчет ссылок, когда память на самом деле изменяется во время выполнения.

Внутри my_fun y находится ссылка , которая относится к функции. Но возвращаемое значение ограничено областью действия вызывающего объекта. (Функции было бы невозможно использовать, если бы это было не так, не имея к этому никакого отношения amp;mut .)