智能指针是一种数据结构,其行为类似于指针,同时提供内存管理或绑定检查等附加功能。
智能指针可跟踪其指向的内存,还可用于管理其他资源,如Fils句柄和网络连接。
智能指针最初用于C++语言。
引用也是一种指针,但除了引用数据之外,它没有其他功能。引用由&运算符表示。
智能指针提供的功能超出了参考提供的功能。智能指针提供的最常见功能是“引用计数智能指针类型”。此功能能够通过跟踪所有者来拥有多个数据所有者,如果没有所有者,则可以清除数据。
引用是仅借用数据的指针,而智能指针是拥有它们指向的数据的指针。
智能指针的类型:
- Box <T>:Box <T>是一个智能指针,指向在类型为T的堆上分配的数据,其中“T”是数据的类型。它用于将数据存储在堆上而不是堆栈上。
- Deref <T>:Deref <T>是一个智能指针,用于自定义解除引用运算符(*)的行为。
- Drop <T>:Drop <T>是一个智能指针,用于在变量超出范围时从堆内存中释放空间。
- Rc <T>:Rc <T>代表参考计数指针。它是一个智能指针,用于记录存储在堆上的值的引用数。
- RefCell <T>:RefCell <T>是一个智能指针,允许借用可变数据,即使数据是不可变的。这个过程被称为内部可变性。
原文出自【易百教程】,商业转载请联系作者获得授权,非商业转载请保留原文链接:https://www.yiibai.com/rust/rust-smart-pointers.html
Rust中的盒子和指针
根据Rust 0.5的tutorial整理,指针这部分内容应该不会变化了。
(豆瓣的帖子没法排版。排版好的请参考http://corwindong.blogspot.com/2013/01/rust_7.html)
大多数现代语言对于聚合类型(如class, struct, enum)都采用一种“uniform representation”方式表示,将这些类型缺省表示为分配在堆上的内存的指针。Rust则不同,类似于C和C++,Rust直接表示这些类型。也就是说聚合数据在Rust中是未打包的(unboxed)。这意味着当你写下let x = Point {x: 1f, y: 1f};时,你是在栈上创建了一个struct。如果你把它拷贝到一个数据结构中,你拷贝的是整个struct,而不是指针。
对于Point这样的小型结构来说,直接拷贝要比先分配内存后再通过指针访问高效。但是对于大型结构,或者那些有可变数据域的结构,只在栈上或堆上保留一份数据,然后通过指针引用会更好一些。
Rust支持好几种指针(三种)。安全指针@T代表分配在本地堆上的managed boxes;指针~T代表分配在交换堆上的uniquely-owned boxes;而指针&T,代表 borrowed pointers,这种指针可以指向任何内存,同时它们的生命周期由调用栈管理。
所有的指针类型都由一元运算符* 来解除引用。
1 Managed Boxes
Managed Boxes是一种指针,指向堆上分配,具有GC的内存。将一元运算符@ 应用于表达式就建立了一个managed box。生成的盒子包含了表达式的结果。拷贝一个managed box,只是拷贝了指针,而不是盒子中的内容。
let x: @int = @10; // New box
let y = x; // Copy of a pointer to the same box
// x and y both refer to the same allocation. When both go out of scope
// then the allocation will be freed.
一个managed type要么形如@T,要么是任何包含了managed boxes或者其他managed types的类型。
// A linked list node
struct Node {
mut next: MaybeNode,
mut prev: MaybeNode,
payload: int
}
enum MaybeNode {
SomeNode(@Node),
NoNode
}
let node1 = @Node { next: NoNode, prev: NoNode, payload: 1 };
let node2 = @Node { next: NoNode, prev: NoNode, payload: 2 };
let node3 = @Node { next: NoNode, prev: NoNode, payload: 3 };
// Link the three list nodes together
node1.next = SomeNode(node2);
node2.prev = SomeNode(node1);
node2.next = SomeNode(node3);
node3.prev = SomeNode(node2);
Managed Boxes绝不跨越task边界。
2 Owned Boxes
和managed boxes不同的是,owned boxes具有内存独享性,因此两个owned boxes不会指向同一内存。所有跨越全部tasks边界的owned boxes都在一个exchange heap上分配。而它们的唯一所有权特性使得tasks可以高效地交换它们。
由于owned boxes的唯一所有特性,拷贝它们则需要分配一个新的owned box然后复制内容。然而,owned boxes默认使用moved,交换内存所有权,反初始化前一个owning变量。任何企图在变量被move后访问该变量的操作都将触发编译错误。
let x = ~10;
// Move x to y, deinitializing x
let y = x;
如果你打算拷贝owned box,你必须明确指出:
let x = ~10;
let y = copy x;
let z = *x + *y;
assert z == 20;
当owned boxes不包含任何managed boxes时,可以发送给其他task。发送方task将交出box的所有权,发送后将再不能访问它。接收方task将成为box的唯一所有者。
3 Borrowed Pointers
Rust的borrowed pointers是通用的引用/指针类型,类似C++的引用类型,但是保证指向有效内存。和owned boxes不同的是,borrowed pointers从不隐含内存所有权。指针可以从任意类型借来,同时保证指针不会比它指向的值生存得更久。
举个例子,比如一个简单的结构类型:Point
struct Point {
x: float, y: float
}
我们使用这个简单的类型来展示指针的多种分配方式。例如,在下面的代码中,三个局部变量都包含一个point,但是分配在不同的位置上。
let on_the_stack : Point = Point {x: 3.0, y: 4.0};
let managed_box : @Point = @Point {x: 5.0, y: 1.0};
let owned_box : ~Point = ~Point {x: 7.0, y: 9.0};
假如我们想要写一个计算两个点距离的函数,要求不管两个点存储在什么位置都能用。例如,我们可能计算on_the_stack和managed_box之间的距离,或者managed_box和owned_box之间的距离。一种方法是定义两个参数都是Point类型的函数,该函数使用point的值。这就会造成我们调用函数时拷贝point的值。对于points而言,问题还不严重,但是一般情况下拷贝都是代价高昂的,甚至更糟的是,存在可变数据域,这些将改变程序的语意。所以我们一般定义接受指向points的指针的函数。我们使用borrowed_pointers来达成目标:
fn compute_distance(p1: &Point, p2: &Point) -> float {
let x_d = p1.x - p2.x;
let y_d = p1.y - p2.y;
sqrt(x_d * x_d + y_d * y_d)
}
现在我们可以以各种方式调用compute_distance():
compute_distance(&on_the_stack, managed_box);
compute_distance(managed_box, owned_box);
这里的&运算符用来取得变量on_the_stack的地址;我们称这个为borrowing局部变量on_the_stack,因为我们创建了一个别名:也就是访问同一数据的另一种方式。
而对于像managed_box和owned_box这样的盒子,则不需要什么操作。编译器能自动将类似@point或~point这样的盒子转换为类似&point这样的borrowed pointer。这是另一种形式的borrowing;这种情况下,managed/owned box的内容被借出。
不论一个值何时被借出,对于原始值得操作都存在一些限制。例如,如果一个变量的内容已经被借出,那么我们不能将该变量发送给别的task,也不能执行任何会使得借出的值被释放,或者类型改变的操作。请牢记这条原则:你必须等借出的值被归还后(也就是等borrowed pointer离开作用域),才能再次对它为所欲为。
对于borrowed pointers更进一步的解释,请参考borrowed pointer tutorial。
4 Dereferencing pointers
Rust使用一元运算符* 来获取box或者pointer的值,这点和C类似。
let managed = @10;
let owned = ~20;
let borrowed = &30;
let sum = *managed + *owned + *borrowed;
对可变指针解引用可以出现在赋值式的左边。这样的赋值将修改指针指向的值。
let managed = @mut 10;
let owned = ~mut 20;
let mut value = 30;
let borrowed = &mut value;
*managed = *owned + 10;
*owned = *borrowed + 100;
*borrowed = *managed + 1000;
指针相关操作符具有高优先级,但是比取数据域和调用方法的点操作优先级低。
let start = @Point { x: 10f, y: 20f };
let end = ~Point { x: (*start).x + 100f, y: (*start).y + 100f };
let rect = &Rectangle(*start, *end);
let area = (*rect).area();
为了消除上面那丑陋的星号括号,点操作符对接受者(点号的左边)自动解引用,所以大多数情况下,不需要对接受者显式解引用。
let start = @Point { x: 10f, y: 20f };
let end = ~Point { x: start.x + 100f, y: start.y + 100f };
let rect = &Rectangle(*start, *end);
let area = rect.area();
来个极端的例子,让编译器对任意重指针自动解引用:
let point = &@~Point { x: 10f, y: 20f };
io::println(fmt!("%f", point.x));
索引操作符([ ])也具有自动解引用的特性。