Почему в сопоставлении с образцом кортежа Rust требуется явное заимствование?
Я пишу бинарное дерево на Rust, и проверка заимствований действительно смущает меня. Вот минимальный пример, который воспроизводит проблему.
Двоичное дерево определяется следующим образом:
struct NonEmptyNode;
pub struct BinaryTree {
root: Option<NonEmptyNode>,
}
Код, отклоненный контролером заимствований:
// Implementation #1
fn set_child_helper(&self, bt: &Self, setter: fn(&NonEmptyNode, &NonEmptyNode)) -> bool {
match (self.root, bt.root) {
(Some(ref rt), Some(ref node)) => {
setter(rt, node);
true
}
_ => false,
}
}
Сообщение об ошибке
error[E0507]: cannot move out of borrowed content
--> src/main.rs:10:16
|
10 | match (self.root, bt.root) {
| ^^^^ cannot move out of borrowed content
error[E0507]: cannot move out of borrowed content
--> src/main.rs:10:27
|
10 | match (self.root, bt.root) {
| ^^ cannot move out of borrowed content
Чтобы заставить его работать, код должен быть изменен на:
// Implementation #2
fn set_child_helper(&self, bt: &Self, setter: fn(&NonEmptyNode, &NonEmptyNode)) -> bool {
match (&self.root, &bt.root) {
// explicit borrow
(&Some(ref rt), &Some(ref node)) => {
// explicit borrow
setter(rt, node);
true
}
_ => false,
}
}
Если я сопоставлю шаблон по одной переменной за раз без явного заимствования, средство проверки заимствования не будет жаловаться вообще:
// Implementation #3
fn set_child_helper(&self, bt: &Self, setter: fn(&NonEmptyNode, &NonEmptyNode)) -> bool {
match self.root {
Some(ref rt) => match bt.root {
// No explict borrow will be fine
Some(ref node) => {
// No explicit borrow will be fine
setter(rt, node);
true
}
_ => false,
},
_ => false,
}
}
Почему реализация № 3 не требует явного заимствования, а реализация № 1 требует?
1 ответ
Ключ в том, что self.root
а также bt.root
являются "выражением места", а кортеж - нет. Причина № 3 в том, что компилятор знает, как "пробиться" через промежуточные выражения, чтобы привязаться к исходному месту хранения.
На это можно посмотреть иначе: очень простые выражения, такие как self.root
особенность в том, что они выглядят и ведут себя как значения (и имеют тип значения), но тайно компилятор запоминает, как он достиг этого значения, позволяя ему вернуться назад и получить указатель на то, откуда это значение было прочитано.
Самый простой способ понять, является ли выражение выражением "место", состоит в том, чтобы попытаться присвоить ему значение. Если вы можете сделать expr = some_value;
, затем expr
должно быть "выражением места". Кстати, это также, почему, когда вы пишете &self.root
, вы получите указатель на где self.root
хранится, а не указатель на копию self.root
,
Этот бизнес "выражения места" не работает для кортежей, потому что у них нет этого свойства. Чтобы создать кортеж, компилятор должен фактически прочитать значения для элементов кортежа и переместить или скопировать их в новое хранилище для кортежа. Это разрушает любые ассоциации мест, которые мог иметь компилятор: эти значения буквально больше не там, где они были раньше.
Наконец, вы можете посмотреть на Option::as_ref
, который превращается &Option<T>
в Option<&T>
, Это позволит вам соответствовать (self.root.as_ref(), bt.root.as_ref())
с такими узорами, как (Some(rt), Some(node))
, что может быть более удобным.