经过更多研究,似乎没有漂亮的方法可以在不直接访问结构字段的情况下实现部分借用,至少目前是这样。
不过,我想稍微讨论一下这个问题的多种不完美的解决方案,以便任何可能最终来到这里的人都可以自己权衡利弊。
我将在这里以the code from my original question 为例进行说明。
假设你有一个结构:
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) 这样的代码需要到处重复,这使得这种方法既脆弱又麻烦。
那么你怎么解决这个问题呢?
A) 一次性借用所有东西
由于不允许多次可变借用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
}
}
}
}
这显然是一种解决方法,远非理想,但它有效,并且允许您将字段本身保密(尽管您可能需要在一定程度上公开实现,因为用户必须将必要的数据交给其他人手动函数)。
B) 使用宏
如果您只担心代码重用并且可见性对您来说不是问题,您可以编写如下宏:
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) 的所有好处,但使用 unsafe 可以轻松完成的事情 如果只有语言有一些用于实际部分借用的语法 /s>,对我来说似乎有点难看。另一方面,它可能(在某些情况下)比解决方案 A)更可取,因为它非常笨重......
编辑:经过一番思考,我意识到,我们知道这种方法在这里是安全的,只是因为我们知道实施。在不同的用例中,有问题的数据实际上可能包含在一个字段中(例如 Map),即使看起来我们在从外部调用时访问的是两种截然不同的数据。
这就是为什么最后一种方法是不安全的,因为借用检查器无法在不暴露私有字段的情况下检查我们是否真的借用了不同的东西,从而使我们的努力毫无意义。
与我最初的想法相反,这甚至无法通过扩展语言来真正改变。 The reason is that one would still need to expose information about private fields in some way for this to work.
写完这个答案后,我还发现了this blog post,它更深入地探讨了可能的解决方案(还提到了一些我没有想到的先进技术,但这些技术都不是普遍适用的)。如果您碰巧知道其他解决方案或改进此处提出的解决方案的方法,请告诉我。