指针

指针

    &[T]是指针,指向一个数组切片;

    &str是“指针”,指向一个字符串切片;

它们不仅包含了指向数据的指针,还携带了所指向的数据的长度信息,但它们对指向的数组/字符串切片没有所有权,不负责内存空间的分配和释放。

    Box<T>是“指针”,指向一个在堆上分配的对象;

    Vec<T>是“指针”,指向一组同类型的顺序排列的堆上分配的对象;

    String是“指针”,指向的是一个堆上分配的字节数组,其中保存的内容是合法的 utf8 字符序列。

它们都对所指向的内容拥有所有权,管理着它们所指向的内存空间的分配和释放。

Rc和Arc也算是某种形式的“指针”,它们提供的是一种“共享”的所有权,当所有的引用计数指针都销毁之后,它们所指向的内存空间才会被释放。

常用指针

  • Box 类型,类似 unique_ptr 类型,代表这个指针对它所指向的内容拥有所有权,有修改权限,负责内存的分配和释放。如果要修改所指向的内容,需要变量绑定有 mut 修饰。
  • & 类型,借用指针,也叫 reference 引用。代表这个指针可以读它指向的内容,没有修改权限,也没有释放权限。
  • &mut 类型,可变借用指针。代表这个指针可以读写它指向的内容,有修改权限,没有释放权限。
  • Rc 类型,引用计数智能指针。它允许多个 Rc 指针指向同一块内存,而且每个 Rc 之间是平等的。当所有 Rc 都消亡后,它指向的内容就会被释放。
  • Cow 类型,写时复制智能指针。它允许在只读的时候使用共享引用,需要修改的时候,再拷贝一份新的内容。

内部可变性

引入:

fn main() {
  let mut data = 100_i32;
  let p : &i32 = &data;
  data = 10;
  println!("{}", *p);
}

是有问题的,使用Cell解决:

use std::cell::Cell;

fn main() {
  let data : Cell<i32> = Cell::new(100);
  let p = &data;
  data.set(10);
  println!("{}", p.get());

  p.set(20);
  println!("{:?}", data);
}

Cell类型的存在,仅仅是为了避开编译器限制

内部可变性之 RefCell

use std::cell::RefCell;
use std::rc::Rc;

fn main() {
    let shared_vec: Rc<RefCell<isize>> = 
        Rc::new(RefCell::new(vec![1, 2, 3]));
    let shared1 = shared_vec.clone();
    let shared2 = shared1.clone();

    shared1.borrow_mut().push(4);
    shared2.borrow_mut().push(5);

    println!("{:?}", shared_vec);
}

只读引用和内部可变性

Rust中提供了只读引用的类型有这几种指针&、Rc、Arc等,它们可以提供alias。Rust中提供了内部可变性的类型有Cell、RefCell、Mutex、RwLock以及Atomic*系列类型等。这两类类型经常需要配合使用。

Rc<Cell<isize>> 使用示例

在某些场景下,我们需要为了生命周期管理的方便,选择具有“共享性”特点的指针,同时又需要通过这样的指针修改所指向的内容。那么,我们就需要用 &/Rc/Arc 指向一个具备“内部可变性”的类型

use std::rc::Rc;
use std::cell::Cell;

fn increase(arg: Rc<Cell<isize>>) {
    let temp = arg.get() + 1;
    arg.set(temp);
}

fn main() {
    let r = Rc::new(Cell::new(1_isize));
    increase(r.clone());
    println!("{}", r.get());
}

ARC & RC & RefCell & Cell

  • Arc是Rc的线程安全版本

  • 它跟 Rc 最大的区别在于,引用计数用的是原子整数类型

  • 与Rc类似,根据Rust的“共享不可变,可变不共享”原则,Arc既然提供了共享引用,就一定不能提供可变性。所以,Arc也是只读的。

Rc是非线程安全的,Arc则是与它对应的线程安全版本。当然还有弱指针Weak也是一一对应的。Rc无需考虑多线程场景下的问题,因此它内部只需普通整数做引用计数即可。Arc要用在多线程场景,因此它内部必须使用“原子整数”来做引用计数

RefCell是非线程安全的,它不能在跨线程场景使用。Mutex/RwLock 则是与它相对应的线程安全版本。它们都提供了“内部可变性”,RefCell无需考虑多线程问题,所以它内部只需一个普通整数做借用计数即可。 Mutex/RwLock 可以用在多线程环境,所以它们内部需要使用操作系统提供的原语来完成锁功能。它们有相似之处,也有不同之处。Mutex/RwLock 在加锁的时候返回的是 Result 类型,是因为它们需要考虑 “异常安全” 这个问题,在多线程环境下,很可能出现一个线程发生了 panic,导致 Mutex 内部的数据已经被破坏,而在另外一个线程中依然有可能观察到这个被破坏的数据结构。RefCell则相对简单,只需考虑AssertUnwindSafe即可。

Cell是非线程安全的,Atomic系列类型则是与它对应的线程安全版本。它们之间的相似之处在于,都提供了“内部可变性”,而且读/写操作都是一条语句就可以完成,无需调用 borrow/lock 之类的方法。它们的不同之处在于,Cell 的条件更宽松,它包含的数据只要是 Copy 即可。而标准库提供的 Atomic* 系列类型则受限于CPU提供的原子指令,无法推广到所有的 Copy 类型。