Есть ли у изменяемых ссылок rust семантика перемещения?

fn main() {
    let mut name = String::from("Charlie");
    let x = &mut name;
    let y = x;       // x has been moved
    say_hello(y);
    say_hello(y);       // but y has not been moved, it is still usable
    change_string(y);
    change_string(y);  

}

fn say_hello(s: &str) {
    println!("Hello {}", s);
}

fn change_string(s: &mut String) {
    s.push_str(" Brown");
}

Когда я назначаю x к y xбыл перемещен. Однако я ожидал, что что-то с семантикой перемещения будет перемещено, когда я использую его в функции. Однако я все еще могу использовать ссылку после последующих звонков. Возможно, это связано с тем, что say_hello() принимает неизменяемую ссылку, но change_string() принимает изменяемую ссылку, но ссылка все еще не перемещается.

1 ответ

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

Семантика перемещения обычно применяется в Rust для всех типов, которые не реализуют Copyчерта. Общие ссылкиCopy, поэтому они просто копируются при назначении или передаче функции. Изменяемые ссылки неCopy, поэтому их следует переместить.

Вот где начинается волшебство. Каждый раз, когда изменяемая ссылка присваивается имени с типом, уже известным компилятору как изменяемая ссылка, исходная ссылка неявно заимствуется, а не перемещается. Итак, функция называется

change_string(y);

трансформируется компилятором в значение

change_string(&mut *y);

Исходная ссылка разграничивается, и создается новое изменяемое заимствование. Это новое заимствование перемещается в функцию, а исходное заимствование освобождается после возврата из функции.

Обратите внимание, что это не разница между вызовами функций и назначениями. Неявная переборка происходит всякий раз, когда компилятор уже знает, что целевой тип является изменяемой ссылкой, например, потому что шаблон имеет явную аннотацию типа. Таким образом, эта строка также создает неявный повторный заимствование, поскольку мы явно аннотировали ее как изменяемый ссылочный тип:

let y: &mut _ = x;

С другой стороны, этот вызов функции перемещает (и, таким образом, потребляет) изменяемую ссылку y:

fn foo<T>(_: T) {}

[...]
foo(y);

Общий тип T здесь явно не является изменяемым ссылочным типом, поэтому неявного повторного заимствования не происходит, даже если компилятор делает вывод, что тип является изменяемой ссылкой - так же, как в случае вашего присвоения let y = x;.

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

fn bar<T>(_a: T, _b: T) {}

fn main() {
    let mut i = 42;
    let mut j = 43;
    let x = &mut i;
    let y = &mut j;
    bar(x, y);   // Moves x, but reborrows y.
    let _z = x;  // error[E0382]: use of moved value: `x`
    let _t = y;  // Works fine. 
}

При выводе типа первого параметра компилятор еще не знает, что это изменяемая ссылка, поэтому неявного повторного заимствования не происходит и xперемещен в функцию. Однако при достижении второго параметра компилятор уже сделал вывод, чтоT является изменяемой ссылкой, поэтому yнеявно заимствуется. (Этот пример является хорошей иллюстрацией того, почему добавление магии компилятора для того, чтобы все "просто работало" - это вообще плохая идея. Явное лучше, чем неявное.)

К сожалению, это поведение в настоящее время не описано в справочнике Rust.

Смотрите также:

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