С первым примером все просто:
struct Test;
fn main() {
let t = &mut Test;
{
let x = t;
}
let y = t;
}
Так как тип &mut Test
некопируемый, t
перемещается в x
, затем x
дропается, в последней строке получаем ошибку - использование перемещенной переменной t
.
Второй пример хитрее:
struct Test;
fn main() {
let t = &mut Test;
{
let x:&mut Test = t;
}
let y = t;
}
Теперь явно указан тип временной переменной x
. Но это в корне все меняет - пример нормально компилируется и запускается.
Отчасти ответ дан в Растономиконе в главе про живучесть(Liveness) ссылок (перевод). Там описывается механизм под названием передача заимствования (reborrowing).
Вот пример оттуда, который отлично все объясняет:
let x = &mut (1, 2);
{
// reborrow x to a subfield
let y = &mut x.0;
// y is now live, but x isn't
*y = 3;
}
// y goes out of scope, so x is live again
*x = (5, 7);
Наш пример эквивалентен такому коду:
struct Test;
fn main() {
let t = &mut Test;
{
//reborrow t
let x = &mut*t;
// x is now live, but t isn't
}
// x goes out of scope, so t is live again
let y = t;
}
Отличие только в том, что у нас происходит передача заимствования на всю структуру, а не на ее часть.
Похоже на взятие мутабельной ссылки на мутабельную ссылку, но в результате получаем копию исходной ссылки а не ссылку на ссылку. Довольно удобно на самом деле.
Почему же указание типа переменной приводит к неявному включению механизма передачи заимствования:
let x:&mut Test = t; //implicit reborrowing
Подозреваю, что это сделано для того, чтобы при передаче параметров в функцию по мутабельной ссылке нам не приходилось каждый раз повторять этот замечательный шаблон: &mut*t
.
Т.е. такой код будет нормально работать:
fn mut_some<T>(_:&mut T) { /*something usefull*/ }
let t = &mut Test;
mut_some(t); //implicit reborrowing
mut_some(t);
mut_some(t);
А вот такой - нет:
fn move_some<T>(_:T) { /*something usefull*/ }
let t = &mut Test;
move_some(t); //no implicit reborrowing
move_some(t);
move_some(t);
Первый же вызов move_some(t)
"съедает" переменную t
.
Придется сделать явную передачу заимствования:
fn move_some<T>(_:T) { /*something usefull*/ }
let t = &mut Test;
move_some(&mut*t); //explicit reborrowing
move_some(&mut*t);
move_some(&mut*t);
Может всё связано с неявным выводом лайфтайма, ведь переменная в внутреннем скопе с новым лайфтаймом, а значит их типы отличаются (хоть и по лайфтайму) с внешней переменной, непонятно только какой лайфтайм будет у переменной, если тип не указан, такой же как у внешней переменной? Нужно будет посмотреть разницу в mir'e сгенерированном. А еще, если все из-за разных типов(по лайфтайму), то в компилятор есть более общий механизм: Deref coercion, про который в книге написано, что компилятор добавит столько * сколько нужно, чтобы привести к нужному типу.