#rust #borrow-checker
#Ржавчина #заимствование-проверка
Вопрос:
Я столкнулся с проблемой, которая вынуждает меня разделить хороший oneliner на {}
блок с промежуточным let
значением. Причина этого мне вообще не ясна. Я смог изолировать проблему в этом минимальном примере:
struct AB {
a: u8,
b: u8,
}
impl AB {
fn foo(amp;self) -> String {
String::from("foo")
}
fn bar(self, x: String) -> String {
format!("{} - {} - {}!", x, self.a, self.b)
}
}
fn main() {
let x = AB { a: 3, b: 5 };
let result = x.bar(x.foo());
println!("{}", result);
}
У меня создалось впечатление, что аргументы bar
функции будут вычислены перед вызовом bar
. foo
заимствуется x
во время его выполнения, но когда он возвращает свое String
, заимствование завершено, поскольку String
время жизни ссылочного подшипника x
не указано. Когда bar
вызывается, foo
заимствование должно закончиться.
Однако компилятор не согласен:
error[E0382]: borrow of moved value: `x`
--> src/main.rs:17:24
|
17 | let result = x.bar(x.foo());
| - ^ value borrowed here after move
| |
| value moved here
|
= note: move occurs because `x` has type `AB`, which does not implement the `Copy` trait
Я не согласен с тем фактом, что bar
действительно перемещается x
. Моя проблема связана с оператором, который foo
заимствует x
после выполнения перемещения.
Простое (но уродливое) исправление:
struct AB {
a: u8,
b: u8,
}
impl AB {
fn foo(amp;self) -> String {
String::from("foo")
}
fn bar(self, x: String) -> String {
format!("{} - {} - {}!", x, self.a, self.b)
}
}
fn main() {
let x = AB { a: 3, b: 5 };
let y = x.foo();
let result = x.bar(y);
println!("{}", result);
}
разделение присваивания x.foo()
промежуточной переменной y
отлично компилируется, подтверждая мое ожидание, что заимствование действительно заканчивается после foo
возврата, но почему это работает? Есть ли что-то, чего я не понимаю в порядке вычисления? Почему я не могу избавиться от промежуточного let y
?
Ответ №1:
Порядок вычисления, с целью заимствования, слева направо.
Это означает, что объект bar
вызова, упоминание о «перемещении» x
, рассматривается перед упоминанием о «заимствовании» x
в foo
вызове, и, следовательно, компилятор считает, что переменная была перемещена из.
Для аналогичного случая, когда внешнее упоминание является изменяемым заимствованием, RFC 2025 был принят в качестве решения, но еще не был реализован. К сожалению, этот RFC, похоже, не охватывает ваш случай, где внешним использованием является перемещение.
Комментарии:
1. Значит, порядок вычисления проверки заимствования отличается от реального порядка выполнения?
2. Я думаю, что часть моей путаницы происходит от sugar, который является синтаксисом метода в стиле ООП.
x(a,b,c)
выглядит как функция, включающая 3 аргумента. Оценка слева направо имеет смысл здесь, но посколькуx
находится «за скобками», я ошибочно посчитал это «не частью самого вызова функции». Но, действительно, рассматривая это какAB::bar(x, x.foo())
, становится ясно. Спасибо3. Тогда почему это работает?
let mut v = vec![0, 1, 2]; v.push(v.len());
4. @MathijsKwik Я не думаю, что «порядок оценки» — это правильный способ думать об этом: время жизни выводится , а не оценивается. Двухфазное заимствование исправляет
amp;mut
случай, разделяя заимствование на два с разным временем жизни, но перемещение не является заимствованием и не имеет времени жизни, поэтому оно действительно сильно отличается семантически. Тем не менее, я согласен, что похоже, чтоself
-принимающий случай также может быть обработан аналогичным образом.5. @nalzok При двухфазном заимствовании (что, чтобы было понятно, происходит в
v.push(v.len())
)amp;mut
заимствование первоначально обрабатывается как общее заимствование, которое затем «обновляется» до полного изменяемого заимствования для вызова функции. На самом деле я не могу более точно изложить концепцию, чем в руководстве по разработке rustc , что также подтверждает ваше подозрение, что сv.push
происходит что-то забавное; однако на этот раз это неDeref
, а autoref .