当处理复合结构时,可变引用的互斥性可能会非常受限。借用检查器(又名 borrowck)理解一些基本的东西,但很容易出错。它确实充分理解结构体,知道可以同时借用结构体的不相交字段。所以这在今天是可以工作的
#![allow(unused)]
fn main () {
struct Foo {
a: i32 ,
b: i32 ,
c: i32 ,
}
let mut x = Foo {a: 0 , b: 0 , c: 0 };
let a = &mut x.a;
let b = &mut x.b;
let c = &x.c;
*b += 1 ;
let c2 = &x.c;
*a += 10 ;
println! ("{} {} {} {}" , a, b, c, c2);
}
然而,borrowck 不以任何方式理解数组或切片,所以这行不通
#![allow(unused)]
fn main () {
let mut x = [1 , 2 , 3 ];
let a = &mut x[0 ];
let b = &mut x[1 ];
println! ("{} {}" , a, b);
}
error[E0499]: cannot borrow `x[..]` as mutable more than once at a time
--> src/lib.rs:4:18
|
3 | let a = &mut x[0];
| ---- first mutable borrow occurs here
4 | let b = &mut x[1];
| ^^^^ second mutable borrow occurs here
5 | println!("{} {}", a, b);
6 | }
| - first borrow ends here
error: aborting due to previous error
虽然 borrowck 可能理解这个简单的情况,但对于 borrowck 来说,要理解像树这样的通用容器类型中的不相交性,显然是无望的,特别是当不同的键实际上*确实*映射到相同的值时。
为了“教导”borrowck 我们所做的事情是正确的,我们需要降级到不安全的代码。例如,可变切片公开了一个 split_at_mut
函数,它会消耗切片并返回两个可变切片。一个用于索引左侧的所有内容,另一个用于索引右侧的所有内容。直观上我们知道这是安全的,因为切片不重叠,因此没有别名。然而,实现需要一些不安全性
#![allow(unused)]
fn main () {
use std::slice::from_raw_parts_mut;
struct FakeSlice <T>(T);
impl <T> FakeSlice<T> {
fn len (&self ) -> usize { unimplemented! () }
fn as_mut_ptr (&mut self ) -> *mut T { unimplemented! () }
pub fn split_at_mut (&mut self , mid: usize ) -> (&mut [T], &mut [T]) {
let len = self .len();
let ptr = self .as_mut_ptr();
unsafe {
assert! (mid <= len);
(from_raw_parts_mut(ptr, mid),
from_raw_parts_mut(ptr.add(mid), len - mid))
}
}
}
}
这实际上有点微妙。为了避免对同一个值创建两个 &mut
,我们通过原始指针显式构造全新的切片。
然而,更微妙的是,产生可变引用的迭代器是如何工作的。迭代器 trait 定义如下
#![allow(unused)]
fn main () {
trait Iterator {
type Item ;
fn next (&mut self ) -> Option <Self::Item>;
}
}
根据此定义,Self::Item
与 self
之间*没有*联系。这意味着我们可以连续多次调用 next
,并*同时*保持所有结果。这对于按值迭代器来说是完全可以的,它们具有完全相同的语义。对于共享引用来说,实际上也可以,因为它们允许任意多个对同一事物的引用(尽管迭代器需要与被共享的事物分开)。
但是可变引用使这变得一团糟。乍一看,它们似乎与此 API 完全不兼容,因为它会生成对同一对象的多个可变引用!
然而,它实际上*确实*可以工作,正是因为迭代器是一次性对象。IterMut 产生的所有内容最多只产生一次,因此我们实际上永远不会产生对同一块数据的多个可变引用。
也许令人惊讶的是,可变迭代器不需要为许多类型实现不安全的代码!
例如,这是一个单链表
fn main () {}
type Link <T> = Option <Box <Node<T>>>;
struct Node <T> {
elem: T,
next: Link<T>,
}
pub struct LinkedList <T> {
head: Link<T>,
}
pub struct IterMut <'a , T: 'a >(Option <&'a mut Node<T>>);
impl <T> LinkedList<T> {
fn iter_mut (&mut self ) -> IterMut<T> {
IterMut(self .head.as_mut().map(|node| &mut **node))
}
}
impl <'a , T> Iterator for IterMut<'a , T> {
type Item = &'a mut T;
fn next (&mut self ) -> Option <Self::Item> {
self .0 .take().map(|node| {
self .0 = node.next.as_mut().map(|node| &mut **node);
&mut node.elem
})
}
}
这是一个可变切片
fn main () {}
use std::mem;
pub struct IterMut <'a , T: 'a >(&'a mut [T]);
impl <'a , T> Iterator for IterMut<'a , T> {
type Item = &'a mut T;
fn next (&mut self ) -> Option <Self::Item> {
let slice = mem::take(&mut self .0 );
if slice.is_empty() { return None ; }
let (l, r) = slice.split_at_mut(1 );
self .0 = r;
l.get_mut(0 )
}
}
impl <'a , T> DoubleEndedIterator for IterMut<'a , T> {
fn next_back (&mut self ) -> Option <Self::Item> {
let slice = mem::take(&mut self .0 );
if slice.is_empty() { return None ; }
let new_len = slice.len() - 1 ;
let (l, r) = slice.split_at_mut(new_len);
self .0 = l;
r.get_mut(0 )
}
}
这是一个二叉树
fn main () {}
use std::collections::VecDeque;
type Link <T> = Option <Box <Node<T>>>;
struct Node <T> {
elem: T,
left: Link<T>,
right: Link<T>,
}
pub struct Tree <T> {
root: Link<T>,
}
struct NodeIterMut <'a , T: 'a > {
elem: Option <&'a mut T>,
left: Option <&'a mut Node<T>>,
right: Option <&'a mut Node<T>>,
}
enum State <'a , T: 'a > {
Elem(&'a mut T),
Node(&'a mut Node<T>),
}
pub struct IterMut <'a , T: 'a >(VecDeque<NodeIterMut<'a , T>>);
impl <T> Tree<T> {
pub fn iter_mut (&mut self ) -> IterMut<T> {
let mut deque = VecDeque::new();
self .root.as_mut().map(|root| deque.push_front(root.iter_mut()));
IterMut(deque)
}
}
impl <T> Node<T> {
pub fn iter_mut (&mut self ) -> NodeIterMut<T> {
NodeIterMut {
elem: Some (&mut self .elem),
left: self .left.as_mut().map(|node| &mut **node),
right: self .right.as_mut().map(|node| &mut **node),
}
}
}
impl <'a , T> Iterator for NodeIterMut<'a , T> {
type Item = State<'a , T>;
fn next (&mut self ) -> Option <Self::Item> {
match self .left.take() {
Some (node) => Some (State::Node(node)),
None => match self .elem.take() {
Some (elem) => Some (State::Elem(elem)),
None => match self .right.take() {
Some (node) => Some (State::Node(node)),
None => None ,
}
}
}
}
}
impl <'a , T> DoubleEndedIterator for NodeIterMut<'a , T> {
fn next_back (&mut self ) -> Option <Self::Item> {
match self .right.take() {
Some (node) => Some (State::Node(node)),
None => match self .elem.take() {
Some (elem) => Some (State::Elem(elem)),
None => match self .left.take() {
Some (node) => Some (State::Node(node)),
None => None ,
}
}
}
}
}
impl <'a , T> Iterator for IterMut<'a , T> {
type Item = &'a mut T;
fn next (&mut self ) -> Option <Self::Item> {
loop {
match self .0 .front_mut().and_then(|node_it| node_it.next()) {
Some (State::Elem(elem)) => return Some (elem),
Some (State::Node(node)) => self .0 .push_front(node.iter_mut()),
None => if let None = self .0 .pop_front() { return None },
}
}
}
}
impl <'a , T> DoubleEndedIterator for IterMut<'a , T> {
fn next_back (&mut self ) -> Option <Self::Item> {
loop {
match self .0 .back_mut().and_then(|node_it| node_it.next_back()) {
Some (State::Elem(elem)) => return Some (elem),
Some (State::Node(node)) => self .0 .push_back(node.iter_mut()),
None => if let None = self .0 .pop_back() { return None },
}
}
}
}
所有这些都是完全安全的,并且可以在稳定的 Rust 上工作!这最终源于我们之前看到的简单结构体情况:Rust 知道您可以安全地将可变引用拆分为子字段。然后,我们可以通过 Options(或者对于切片,用空切片替换)来编码永久消耗引用。