一、智能指针
一般来说,有过C/C++语言编程的人来说,对智能指针并不陌生。所谓智能指针,并不是普通人想象的指针是智能的,它其实是对原生指针的一种封装,这种封装通过引用计数器的方法来判断指针是否还在使用,从而自动的管理指针对应的内存资源回收。之所以叫智能,应该是和原生指针的手动处理对应的称谓。不过智能指针并不是万能的,这个有兴趣可以参看以前的C/C++相关的博客文章,使用不当,仍然会有内存资源的泄露。
在Rust中,做为号称要取代c++的一种语言,没有智能指针那是说不过去的,既然谈到了智能指针,就不得不提到原生指针,那在Rust中它长什么样子呢?先看一个基础的例子:
let p = &3;
let ptr = p as *const i32;
let p1 = &mut 5.0;
let ptr1 = p1 as *mut f64;
一般来说,在Rust中原生指针是在unsafe块中使用的,和C/C++中有些类似。
在Rust中,最初级的智能指针可以算是Box,它提供了类似于其它语言的装箱操作,它在堆上提供简单的复制操作。其所有权的语义取决于其封装的对象的类型。其针对单一所有权,如果基础类型实现了copy,那么它就是拷贝,否则就是Move。
如果感兴趣可以尝试着去实现一个基础的链表,会发现在实现的过程中Rust对Box的一些用法。
二、Rust中的Rc和Arc
在Rust中一般来说,所有权在同一时刻一般控制在一个所有者之上。这样就保证了资源的安全性。但凡事都有特殊情况,在某些情况下,需要多个同时拥有所有权,而这又分为了两种情况,即单线程和多线程,在Rust中提供了Rc(同一线程内)和Arc(多线程)。先看一下Rc, Rc是一个智能指针,其 中的 T对象是只读的,当引用计数为0时,资源会自动回收(和c++智能指针一样)。为了打破智能指针中的循环引用导致的内存资源泄露的问题,它提供了类似c++智能指针的Weak指针,Rc 最常用的是两个方法:
Rc::new:增加新的引用计数容器。
clone:增加强引用计数并且获取 Rc。
看一个例子:
use std::rc::Rc;
let five = Rc::new(5);
let weak_five = Rc::downgrade(&five);
let strong_five: Option<Rc<_>> = weak_five.upgrade();
它可以用 Rc 调用 downgrade 方法而转换成 Weak也可以把Weak 通过使用 upgrade 方法转换成 Option<Rc>,如果资源已经被释放,则 Option 值为 None(这个和c++中判断NULL基本是一致的)。
既然有同一线程的需求,自然就会有跨线程的需求,这就得使用Arc这个智能指针了,Arc是跨线程的,但它的对象不是只读的。同样,它也有Weak指针应用。看一下它的例子:
use std::sync::Arc;
use std::thread;
fn main() {
let numbers: Vec<_> = (0..100u32).collect();
let shared_numbers = Arc::new(numbers);
for _ in 0..10 {
let child_numbers = shared_numbers.clone();
thread::spawn(move || {
let local_numbers = &child_numbers[..];
// Work with the local numbers
});
}
}
其实这个用法并没有多么复杂,关键的前提是你得掌握多线程中是如何对数据进行操作的,再来有针对的使用哪种智能指针。
三、Rust中的Cell和RefCell
在Rust中,改变一个变量,通常是声明成mut或者借用&mut,但这两种方式都不够灵活,所以Rust提供了Cell和RefCell两种机制。它可以让程序在需要的时候儿,就可以改变对象的值,而不用受限于借用的束缚。Cell只能用于实现了Copy的类型。看个例子:
use std::cell::Cell;
let c = Cell::new(5);
c.set(10);
而其它的情况都适合于RefCell,它是在运行时借用。它应用的场景如下:
1、只能在线程内部使用无法跨线程应用,所以经常和Rc配合使用。
2、需要注意在线程内部包装对象被可变借用两次,会引起线程的崩溃。
看一个例子:
use std::collections::HashMap;
use std::cell::RefCell;
use std::rc::Rc;
fn main() {
let shared_map: Rc<RefCell<_>> = Rc::new(RefCell::new(HashMap::new()));
shared_map.borrow_mut().insert("africa", 92388);
shared_map.borrow_mut().insert("kyoto", 11837);
shared_map.borrow_mut().insert("piccadilly", 11826);
shared_map.borrow_mut().insert("marbles", 38);
}
看一下会崩溃的例子:
use std::cell::RefCell;
use std::thread;
let result = thread::spawn(move || {
let c = RefCell::new(5);
let m = c.borrow_mut();
let b = c.borrow(); // this causes a panic
}).join();
assert!(result.is_err());
================================
use std::cell::RefCell;
use std::thread;
let result = thread::spawn(move || {
let c = RefCell::new(5);
let m = c.borrow();
let b = c.borrow_mut(); // this causes a panic
}).join();
assert!(result.is_err());
两个例子都会崩溃。可见还是不能进行两次以上的可变借用的。
其实看完了这个内容,是不是发现它有点类似于指针是常量但指针指向的内容不是常量的用法。
四、Drop、Deref 和 DerefMut
这三个Trait都是用来实现智能指针的所谓智能的,一个类似于c++的析构函数,后面两个是用来解指针的引用的。析构函数不用过多解释就是对资源的回收和处理,这个解引用其实就有点意思了,一般来说&T和T是一个反向的操作,一个用来取指针,一个用来得到指针对应的对象。
但是Deref 在rust中有一个比较清奇的用法,它可以自动把&&&&连续的取指自动***有几个对应几个。这个有点意思。看个例子:
struct Foo;
impl Foo {
fn foo(&self) { println!("Foo"); }
}
let f = &&Foo;
f.foo();
(&f).foo();
(&&f).foo();
(&&&&&&&&f).foo();
代码大多出自《RustPrimer》。
五、总结
这个总结的比较简单,等回头会对其中的具体的用法进行更详细的说明。其实你会发现,整个Rust的用法似乎是有意无意就是针对c++的用法进行的一种完善或者说修改,二者有些息息相通的意味,细细品,你细细品。
努力吧,归来的少年!