在rust编程语言的官方介绍中,对于Box智能指针介绍比较简单,只说其可以包裹一个变量,将变量存在堆上,Box中包含堆上的地址。同时Box指针实现了自动解引用trait。在实际操作中,也确实类似,对其操作与对普通类型操作类似,支持连续引用和解引用,在编译器不能自动解引用时,需要手动解引用。
Box指针看似简单,实际上在复杂的应用场景中也有很多情况,我们从简单变量的指针开始。
一、简单变量的指针操作
对于简单变量,操作可以如下:(例子-1)
fn main() {
let mut a = 5;
let b = &mut a;
*b += 10;
println!("b = {:?}", b);
println!("a = {:?}", a);
}
也可以连续引用或可变引用:(例子-2)
fn main() {
let mut a = 5;
let mut b = &mut a;
let c = &mut b;
**c += 10;
println!("c = {:?}", c);
println!("b = {:?}", b);
println!("a = {:?}", a);
}
多次引用时,如上面中间一行 “**c + 10”,对c运算,需要手动解引用多次。但下面println!不需要,因为编译器可以自动识别并进行多次解引用。
二、Box指针的引用操作——多重引用和改变Box或其中的值
2.1 改变Box本身的值
那么对于Box指针的操作呢?也类似,可以通过引用或多次引用,使用或改变原始变量的值(需要手动多次解引用),如下代码:
fn main() {
let mut a = Box::new(5);
let b = &mut a;
// let mut b = &mut a;
// let c = &mut b;
// **c = Box::new(10);
*b = Box::new(12);
// println!("c = {:?}", c);
println!("b = {:?}", b);
println!("a = {:?}", a);
}
看起来和普通变量好像没什么区别。
2.2 改变Box内包含的变量的值
但等一下,上面的代码,改变的都是整个Box本身。如果改变Box里面的值呢?可以吗?又怎么操作呢?答案是可以的,如下代码:
fn main() {
let mut a = Box::new(5);
*a += 12;
println!("a = {:?}", a);
}
这也确实和Box文档介绍的一样,它包含了地址,其实可以视为是一个指向堆数据的指针。上面代码和 例子-1 中的代码有点类似了。
这种改变也支持多重连续引用,下面代码有点类似于 例子-2:
fn main() {
let mut a = Box::new(5);
let b = &mut a;
**b += 10;
// *a += 12;
println!("b = {:?}", b);
println!("a = {:?}", a);
}
如果其中包含的是复合类型,也可以通过解引用改变其中的值,该操作支持多重引用,如下:(代码中要注意a.x的类型及操作与a.z的差异)
fn main() {
let mut a = Box::new(Point::new(5, 5, 9));
*a.x += 10; // because a.x's type is Box<i32>, it needs to deref.
a.z += 10; // a.z's type is i32, it doesn't need to deref.
println!("a = {:?}", a);
let b = &mut a;
*b.x += 10;
println!("b = {:?}", b);
println!("a = {:?}", a);
}
#[derive(Debug)]
struct Point {
x: Box<i32>,
y: Box<i32>,
z: i32,
}
impl Point {
fn new(x: i32, y: i32, z: i32) -> Point {
Point {
x: Box::new(x),
y: Box::new(y),
z,
}
}
}
上面代码中,编译器首先对a和b进行了自动解引用,然后才能操作被包含变量的值,输出:
a = Point { x: 15, y: 5, z: 19 }
b = Point { x: 25, y: 5, z: 19 }
a = Point { x: 25, y: 5, z: 19 }
上面一系列的操作还是有点令人眼花缭乱的:)
三、Box指针的解引用
既然Box只是一个指针,那么可以对其进行解引用(*)操作吧?
3.1 简单类型
我们先看看简单类型:
fn main() {
let mut a = Box::new(5);
let b = *a; // copy '*a' to b.
*a += 12;
println!("b = {:?}", b);
println!("a = {:?}", a);
}
答案是可以的,对于简单类型来说,其值是copy的。如上面有注释的行。同时变量a仍然可用。下方通过 *a += 12 改变a的值,可以看出b的值没有变化。输出为:
b = 5
a = 17
对于简单类型来说,引用情况下,允许对引用进行解引用吗?答案是不允许的。下方代码无法编译:
let a = Box::new(5);
let b = &a;
let c = *b; // Error! move occurs because `*b` has type `std::boxed::Box<i32>`, which does not implement the `Copy` trait
提示错误:
cannot move out of `*b` which is behind a shared reference
move occurs because `*b` has type `std::boxed::Box<i32>`, which does not implement the `Copy` trait
这里注意,b为a的引用,其类型为&Box<i32>,对b进行解引用的话,*b的类型才为 Box<i32>,如上错误提示,*b 未实现Copy trait, 并且*b也不拥有其指向变量a的所有权,所以无法移动到变量c中,违反所有权规则,报错。可变引用情况下也是如此。
在这种情况下,可以一直解引用到其包含的值,并进行操作,但此时的值是copy的,可以看出,对c的操作不改变a和b,是可以的,如下代码:
fn main() {
let mut a = Box::new(5);
let b = &mut a;
let mut c = **b;
c += 20;
**b += 10;
println!("c = {:?}", c);
println!("b = {:?}", b);
println!("a = {:?}", a);
}
输出:
c = 25
b = 15
a = 15
3.2 复合类型
那么如果Box中包含的是复合数据类型呢?我们先定义一个复合类型:
#[derive(Debug)]
struct Point {
x: Box<i32>,
y: Box<i32>,
}
impl Point {
fn new(x: i32, y: i32) -> Point {
Point {
x: Box::new(x),
y: Box::new(y),
}
}
}
尝试:
fn main() {
let a = Box::new(Point::new(5, 5));
let b = *a;
println!("b = {:?}", b);
println!("a = {:?}", a);
}
最后一行报错:
borrow of moved value: `a`
value borrowed here after move
note: move occurs because `*a` has type `Point`, which does not implement the `Copy` traitrustc(E0382)
如果注释掉最后一行,则可编译运行,可以看出,对a进行解引用后,将a包含的值的所有权转移到了变量b中(因为Point未实现Copy trait),同时a变得无效,不可再次使用。此时,可以对b进行各种操作,如下:
fn main() {
let a = Box::new(Point::new(5, 5, 9));
let mut b = *a;
b.z += 10;
*b.x += 10;
println!("b = {:?}", b);
// println!("a = {:?}", a);
}
输出:
b = Point { x: 15, y: 5, z: 19 }
那么,引用情况下,允许对引用进行解引用吗?答案也是不允许的。如下代码无法编译:
let a = Box::new(Point::new(5, 5));
let b = &a;
let c = *b;
提示错误:
cannot move out of `*b` which is behind a shared reference
move occurs because `*b` has type `std::boxed::Box<Point>`, which does not implement the `Copy` trait
这和包含简单类型时对引用的解引用错误类似,也和笔记(10)中对struct的深入分析类似。
那么此时可以像简单类型那样,一直解引用到其包含的值,并进行move操作吗?经过实验,是不行的。下面代码无法编译:
fn main() {
let a = Box::new(Point::new(5, 5, 9));
let b = &a;
let c = **b;
println!("b = {:?}", b);
// println!("a = {:?}", a);
}
提示如下错误:
cannot move out of `**b` which is behind a shared reference
move occurs because `**b` has type `Point`, which does not implement the `Copy` trait
如果要改变其中的值,请参考2.2小节。
四、Box包含struct成员的所有权的转移
之前有学习到,struct支持成员的所有权转移,那么当struct被Box包裹时,还支持吗?我们先定义一个结构体及其方法:
#[derive(Debug)]
struct Node {
val: i32,
next: Option<Box<Node>>, // 'next' has the ownership of the next node.
}
impl Node {
fn new(val: i32) -> Box<Node> {
Box::new(Node { val, next: None })
}
fn link(&mut self, node: Box<Node>) {
self.next = Some(node);
}
}
尝试一下:
fn main() {
let mut a = Node::new(1);
let b = Node::new(2);
a.link(b);
let c = a.next;
println!("{:?}", c);
// println!("{:?}", a);
}
a的类型为 Box<Node>,但上面代码仍然可以将a.next 的所有权 move 到 变量 c 中。这相当于下面这行:
let c = (*a).next;
对a进行解引用,获取被包含的Node的所有权,然后对struct成员进行所有权转移。但编译器已经识别到我们的意图了。Box指针不愧是智能指针。
不信的话可以将最后一行打印a的注释去掉,会提示如下错误:
borrow of partially moved value: `a`
说明a的部分成员所有权确实被转移了。
如果对上面的代码加一层引用,则会报错:
let mut a = Node::new(1);
let b = Node::new(2);
a.link(b);
let d = &a;
let c = d.next; // Error!
最后一行会报错:
cannot move out of `d.next` which is behind a shared reference
仔细分析一下,Box智能指针已经自动解引用了,这里报错的原因是d是Node的引用,无权转移其成员的所有权。这和前面学习struct的引用的所有权限制是一致的。
简单吗?一点都不简单。