После некоторых дополнительных исследований кажется, что не существует красивого способа добиться частичного заимствования без прямого доступа к полям структуры, по крайней мере, на данный момент.
Однако есть несколько несовершенных решений, которые я хотел бы немного обсудить, чтобы любой, кто может оказаться здесь, мог взвесить все за и против для себя. Я буду использовать код из моего исходного вопроса в качестве примера, чтобы проиллюстрировать их здесь.
Допустим, у вас есть структура:
struct GraphState {
nodes: Vec<Node>,
edges: Vec<Edge>,
}
и вы пытаетесь заимствовать одну часть этой структуры изменчиво, а другую - неизменяемо:
// THIS CODE DOESN'T PASS THE BORROW-CHECKER
impl GraphState {
pub fn edge_at(&self, edge_index: u16) -> &Edge {
&self.edges[usize::from(edge_index)]
}
pub fn node_at_mut(&mut self, node_index: u8) -> &mut Node {
&mut self.nodes[usize::from(node_index)]
}
pub fn remove_edge(&mut self, edge_index: u16) {
let edge = self.edge_at(edge_index); // first (immutable) borrow here
// update the edge-index collection of the nodes connected by this edge
for i in 0..2 {
let node_index = edge.node_indices[i];
self.node_at_mut(node_index).remove_edge(edge_index); // second (mutable)
// borrow here -> ERROR
}
}
}
}
Но, конечно, это не удается, поскольку вам не разрешено брать self
как изменяемые и неизменяемые одновременно.
Чтобы обойти это, вы можете просто получить прямой доступ к полям:
impl GraphState {
pub fn remove_edge(&mut self, edge_index: u16) {
let edge = &self.edges[usize::from(edge_index)];
for i in 0..2 {
let node_index = edge.node_indices[i];
self.nodes[usize::from(node_index)].remove_edge(edge_index);
}
}
}
Этот подход работает, но у него есть два основных недостатка:
- Доступные поля должны быть общедоступными (по крайней мере, если вы хотите разрешить доступ к ним из другой области). Если это детали реализации, которые вы бы предпочли сохранить в секрете, вам не повезло.
- Вам всегда нужно работать с полями напрямую. Это означает, что код типа
usize::from(node_index)
необходимо повторять повсюду, что делает этот подход хрупким и громоздким.
Так как же решить эту проблему?
А) Брать все сразу
Поскольку одновременное заимствование self
несколько раз не разрешено, взаимное заимствование всех частей одновременно является одним из простых способов решить эту проблему:
pub fn edge_at(edges: &[Edge], edge_index: u16) -> &Edge {
&edges[usize::from(edge_index)]
}
pub fn node_at_mut(nodes: &mut [Node], node_index: u8) -> &mut Node {
&mut nodes[usize::from(node_index)]
}
impl GraphState {
pub fn data_mut(&mut self) -> (&mut [Node], &mut [Edge]) {
(&mut self.nodes, &mut self.edges)
}
pub fn remove_edge(&mut self, edge_index: u16) {
let (nodes, edges) = self.data_mut(); // first (mutable) borrow here
let edge = edge_at(edges, edge_index);
// update the edge-index collection of the nodes connected by this edge
for i in 0..2 {
let node_index = edge.node_indices[i];
node_at_mut(nodes, node_index).remove_edge(edge_index); // no borrow here
// -> no error
}
}
}
}
Это явно обходной путь и далек от идеала, но он работает и позволяет вам сохранить частные поля самих себя (хотя вам, вероятно, придется немного раскрыть реализацию, поскольку пользователь должен вручную передать необходимые данные другим функциям. ).
Б) Используйте макрос
Если повторное использование кода - это все, о чем вы беспокоитесь, а видимость для вас не проблема, вы можете написать такие макросы:
macro_rules! node_at_mut {
($this:ident, $index:expr) => {
&mut self.nodes[usize::from($index)]
}
}
macro_rules! edge_at {
($this:ident, $index:expr) => {
&mut self.edges[usize::from($index)]
}
}
...
pub fn remove_edge(&mut self, edge_index: u16) {
let edge = edge_at!(self, edge_index);
// update the edge-index collection of the nodes connected by this edge
for i in 0..2 {
let node_index = edge.node_indices[i];
node_at_mut!(self, node_index).remove_edge(edge_index);
}
}
}
Если ваши поля в любом случае являются общедоступными, я бы, вероятно, выбрал это решение, так как оно мне кажется наиболее элегантным.
C) Иди небезопасно
То, что мы хотим здесь сделать, очевидно, безопасно. К сожалению, проверяющий заимствования не может этого увидеть, поскольку сигнатуры функций не говорят ему ничего, кроме того, что self
заимствуется. К счастью, Rust позволяет нам использовать ключевое слово unsafe
в таких случаях:
pub fn remove_edge(&mut self, edge_index: u16) {
let edge: *const Edge = self.edge_at(edge_index);
for i in 0..2 {
unsafe {
let node_index = (*edge).node_indices[i];
self.node_at_mut(node_index).remove_edge(edge_index); // first borrow here
// (safe though since remove_edge will not invalidate the first pointer)
}
}
}
Это работает и дает нам все преимущества решения A), но с использованием небезопасного для чего-то , без чего можно было бы легко обойтись, если бы только язык имел некоторый синтаксис для фактического частичного заимствования , мне кажется немного некрасивым. С другой стороны, это может (в некоторых случаях) быть предпочтительнее решения A) из-за его явной неуклюжести ...
РЕДАКТИРОВАТЬ: Подумав, я понял, что мы знаем, что этот подход безопасен здесь, только потому что мы знаем о реализации. В другом варианте использования данные, о которых идет речь, могут фактически содержаться в одном поле (скажем, в карте), даже если кажется, что мы обращаемся к двум очень разным типам данных при вызове извне. Вот почему этот последний подход по праву небезопасен, поскольку программа проверки заимствований не может проверить, действительно ли мы заимствуем разные вещи, без раскрытия закрытых полей, что делает наши усилия бессмысленными.
Вопреки моему первоначальному мнению, это даже не может быть изменено расширением языка. Причина в том, что в некоторых способ, чтобы это работало.
Написав этот ответ, я также нашел это сообщение в блоге, который немного глубже рассматривает возможные решения (а также упоминает некоторые продвинутые методы, которые мне не приходили в голову, хотя ни один из них не применим повсеместно). Если вы знаете другое решение или способ улучшить предложенные здесь, сообщите мне.
person
PSteinhaus
schedule
26.12.2020