rust 智能指针

90 阅读5分钟

重温引用

引用的本质只是表示内存中某些字节起始位置的数字。 它唯一的目的就是表示特定类型的数据存在于何处。引用与数字的不同之处在于, Rust 将验证引用自身的生命周期不会超过它执行的内容。

指针

引用可以转换成一个更原始的类型, 指针。 像数字一样,它可以不受限制的复制和传递,但是Rust不保证它指向的内存位置的有效性。 有两种指针类型:

  • *const T - 指向永远不会改变的T类型数据的指针
  • *mut T - 指向可以更改的T类型数据的指针

指针可以与数字相互转换(例如usize)。
指针可以使用 unsafe 代码访问数据

内存细节:

  • Rust中的引用在用法上与 C 中的指针非常相似,但在如何存储和传递给其他函数上有更多的编译时间限制。
  • Rust中的指针类似于 C 中的指针,它表示一个可以复制或传递的数字,甚至可以转换为数字类型,可以将其修改为数字以进行指针数学运算。
fn main() {
    let a = 42;
    let memory_location = &a as *const i32 as usize;
    println!("Data is here {}", memory_location);
}

解引用

  • & 引用
  • * 解引用 rust 可以自动的去解多重&

智能指针

通常, 智能指针实现了Deref、DerefMut 和 Drop 特征。 以指定当使用* 和 . 运算符解引用应该触发的逻辑。

use std::ops::Deref;

struct TattleTell<T> {
    value: T,
}

impl<T> Deref for TattleTell<T> {
    type Target = T;
    fn deref(&self) -> &Self::Target {
        println!("{} was used!", std::any::type_name::<T>());
        &self.value
    }
}

fn main() {
    let a = 42;
    let memory_location = &a as *const i32 as usize;
    println!("Data is here {}", memory_location);
    let foo = TattleTell {
        value: "secret message",
    };

    println!("{}", foo.len());
}

智能不安全代码

智能指针倾向于经常使用不安全的代码。如前所述,它们是与 Rust 中最低级别的内存进行交互的常用工具。
什么是不安全代码? 不安全代码的行为与普通 Rust 完全一样,除了一些 Rust 编译器无法保证的功能。
不安全代码的主要功能是解引用指针。 这意味着将原始指针指向内存中的某个位置并声明“此处存在数据结构!” 并将其转换为您可以使用的数据表示(例如将*const u8 转换为u8)。 Rust 无法跟踪写入内存的每个字节的含义。 因为 Rust 不能保证在用作 指针 的任意数字上存在什么,所以它将解引用放在一个 unsafe { ... } 块中。

智能指针广泛地被用来解引用指针,它们的作用得到了很好的证明。

fn main() {
    let a: [u8; 4] = [86, 14, 73, 64];
    // this is a raw pointer. Getting the memory address
    // of something as a number is totally safe
    let pointer_a = &a as *const u8 as usize;
    println!("Data memory location: {}", pointer_a);
    // Turning our number into a raw pointer to a f32 is
    // also safe to do.
    let pointer_b = pointer_a as *const f32;
    let b = unsafe {
        // This is unsafe because we are telling the compiler
        // to assume our pointer is a valid f32 and
        // dereference it's value into the variable b.
        // Rust has no way to verify this assumption is true.
        *pointer_b
    };
    println!("I swear this is a pie! {}", b);
}


引用计数

Rc 是一个能将数据从栈移动到智能指针。 它允许我们克隆其他 Rc 智能指针, 这些指针都具有不可改变地借用放在堆上的数据的能力。 只有当最后一个智能指针被删除时,堆上的数据才会被释放。


use std::rc::Rc;

struct Pie;
impl Pie {
    fn eat(&self) {
        println!("tastes better on the heap!");
    }
}
fn main() {
    let heap_pie = Rc::new(Pie);
    let heap_pie2 = heap_pie.clone();
    let heap_pie3 = heap_pie2.clone();
    heap_pie3.eat();
    heap_pie2.eat();
    heap_pie.eat();
}

共享访问

RefCell 是一个容器数据结构,通常由智能指针拥有,它接收数据并让我们借用可变或不可变引用来访问内部内容。 当您要求借用数据时,它通过在运行时强制执行 Rust 的内存安全规则来防止借用被滥用

只有一个可变引用或多个不可变引用,但不能同时有!

如果你违反了这些规则,RefCell 将会panic。


use std::cell::RefCell;

struct Pie {
    slices: u8
}

impl Pie {
    fn eat(&mut self) {
        println!("tastes better on the heap!");
        self.slices -= 1;
    }
}

fn main() {
    // RefCell validates memory safety at runtime
    // notice: pie_cell is not mut!
    let pie_cell = RefCell::new(Pie{slices:8});
    
    {
        // but we can borrow mutable references!
        let mut mut_ref_pie = pie_cell.borrow_mut();
        mut_ref_pie.eat();
        mut_ref_pie.eat();
        
        // mut_ref_pie is dropped at end of scope
    }
    
    // now we can borrow immutably once our mutable reference drops
     let ref_pie = pie_cell.borrow();
     println!("{} slices left",ref_pie.slices);
}

线程间共享

Mutex 是一种容器数据结构,通常由智能指针持有,它接收数据并让我们借用对其中数据的可变和不可变引用。 这可以防止借用被滥用,因为操作系统一次只限制一个 CPU 线程访问数据,阻塞其他线程,直到原线程完成其锁定的借用。

多线程超出了 Rust 之旅的范围,但 Mutex 是协调多个 CPU 线程访问相同数据的基本部分。

有一个特殊的智能指针 Arc,它与 Rc 相同,除了使用线程安全的引用计数递增。 它通常用于对同一个 Mutex 进行多次引用。


use std::sync::Mutex;

struct Pie;

impl Pie {
    fn eat(&self) {
        println!("only I eat the pie right now!");
    }
}

fn main() {
    let mutex_pie = Mutex::new(Pie);
    // let's borrow a locked immutable reference of pie
    // we have to unwrap the result of a lock
    // because it might fail
    let ref_pie = mutex_pie.lock().unwrap();
    ref_pie.eat();
}

组合智能指针

智能指针看起来可能会存在一些限制,但是我们可以做一些非常有用的结合。

Rc<Vec<Foo>> - 允许克隆多个可以借用堆上不可变数据结构的相同向量的智能指针。

Rc<RefCell<Foo>> - 允许多个智能指针可变/不可变地借用相同的结构Foo

Arc<Mutex<Foo>> - 允许多个智能指针以 CPU 线程独占方式锁定临时可变/不可变借用的能力。

内存细节:

  • 您会注意到一个包含许多这些组合的主题。 使用不可变数据类型(可能由多个智能指针拥有)来修改内部数据。 这在 Rust 中被称为“内部可变性”模式。 这种模式让我们可以在运行时以与 Rust 的编译时检查相同的安全级别来改变内存使用规则。