Rust 指针

发布时间: 更新时间: 总字数:1841 阅读时间:4m 作者: IP上海 分享 网址

Rust指针:一个变量在内存中包含的是一个地址(即指向其他数据)。

介绍

Rust 中最常见的指针就是 引用

引用

  • 使用 & 借用它指向的值
  • 没有额外的开销

智能指针

智能指针是这样的一些数据结构:

  • 行为和指针类似
  • 有额外的元数据和功能
  • 很多时候拥有它所指向的数据

智能指针示例:StringVec<T>,特点如下:

  • 都拥有一块内存,且允许用户对其操作
  • 都拥有元数据,如容量等
  • 提供额外的功能或保障(String 保障其数据是合法的 UTF-8 编码)

智能指针的实现:通常使用 struct 实现,并且实现如下 trait:

  • Deref trait:允许智能指针 struct 的实例像引用一样使用
  • Drop trait:允许自定义当智能指针实例走出作用域时的代码

引用技术(reference counting)智能指针类型

  • 通过记录所有者的数量,使一份数据被多个所有者同时持有
  • 当没有任何所有者持有数据时,自动清理数据

标准库中常见的智能指针:

Box<T>

  • Box<T>:最简单的只能指针,它被定义成拥有一个元素的 tuple struct
    • 在 heap 内存上存储数据
    • stack 存储指向 heap 数据的指针
    • 没有性能开销和其他额外的功能
    • 使用场景:
      • 大数据量时,想移交所有权,但需要确保在操作时数据不会被复制
      • 编译时,某类型的大小无法确定
use std::ops::Deref;

struct MyBox<T>(T);

impl<T> MyBox<T> {
    fn new(x: T) -> MyBox<T> {
        MyBox(x)
    }
}

impl<T> Deref for MyBox<T> {
    type Target = T;

    fn deref(&self) -> &T {
        &self.0
    }
}

fn main() {
    let x: Box<i32> = Box::new(5);
    println!("x={}", x);

    let x:i32 = 2;
    let y: &i32 = &x;
    let z: Box<i32> = Box::new(x);

    assert_eq!(2, x);
    assert_eq!(2, *y);
    assert_eq!(2, *z);

    let z: MyBox<i32> = MyBox::new(2);
    assert_eq!(2, *z);
}

Deref trait

  • Deref 解引用
  • Deref trait 可以自定义解引用运算符 * 的行为
  • 实现 Deref trait(需要实现 deref 方法),智能指针可以像常规引用一样来处理
    • 实现后,会被隐式的转化为 *(x.deref())

函数和方法的 隐式解引用转化(deref coercion)

  • 隐式解引用转化(deref coercion) 是为函数和方法提供的一种便捷特性
  • 若 T 实现了 deref trait,则 deref coercion 可把 T 的引用转化为 T 经过 deref 操作后生成的引用
  • 当把某类型的引用传递给函数或方法时,但它的类型与定义的类型不匹配:
    • deref coercion 会自动发生
    • 编译器会对 deref 进行一系列调用(编译时完成,没有额外性能开销),将其转化为所需的参数类型

如:示例中的 MyBox<String> 的引用可以隐式解引用为 &str

  • &x: &MyBox<String> -> deref &String -> deref &str
  • $x -> &(*mybox)[..]

解引用和可变引用

  • 可以使用 DerefMut trait 重载可变引用的 * 运算符
  • 在类型和 trait 在下列三种情况发生时,Rust会执行 deref coercion
    • T: Deref<Target=U>,允许 &T 转化为 &U
    • T: DerefMut<Target=U>,允许 &mut T 转化为 &mut U
    • T: Deref<Target=U>,允许 &mut T 转化为 &U

Drop trait

  • Drop trait 可以让自定义当值将要离开作用域时发生的动作,如:关闭文件、网络资源释放等
  • 任何类型都可以实现 Drop trait,需要实现 drop 方法,Drop trait 在预导入模块里(prelude)导入
    • 按创建反顺序释放
  • Drop traitdrop 方法不允许手动调用(但可以调用 std::mem::drop() 函数提前手动释放),释放处理时自动清理
#[derive(Debug)]
struct CustomPointer {
    data: String,
}

impl Drop for CustomPointer {
    fn drop(&mut self) {
        println!("data is {}", self.data)
    }
}

fn main() {
    let c: CustomPointer = CustomPointer{ data: String::from("abcd") };

    println!("{:?}", c);
    // drop(c);
}

Rc<T>

  • Rc<T>:启用多重所有权的引用计数智能指针类型,通过不可变引用,使程序在不同部分之间共享只读数据
    • rc 是 reference counting 的缩写
    • 一个值会有多个所有者
    • 追踪所有值的引用
    • 当 0 个引用时,该值可以被清理掉
    • Rc<T> 不在预导入模块(prelude),需要手动导入 ``
  • 方法:
    • Rc::clone(&a) 增加引用计数
      • Rc::clone() 增加引用,不会进行数据的深度拷贝操作
      • 类型的 .clone() 方法一般指定类型数据的深拷贝操作
    • Rc::strong_count(&a) 获得引用计数
    • Rc::weak_count() 弱引用计数
  • 使用场景:
    • 单线程场景
    • 需要在 heap 上分配数据,数据被多个部分读取,编译器无法确认那个部分最后使用
#[derive(Debug)]
enum List1 {
    Cons1(i32, Box<List1>),
    Nil1
}

use crate::List1::{Cons1, Nil1};

#[derive(Debug)]
enum List2 {
    Cons2(i32, Rc<List2>),
    Nil2
}

use crate::List2::{Cons2, Nil2};
use std::rc::Rc;

fn main() {
    let a: List1 = Cons1(5,
        Box::new(Cons1(10,
            Box::new(Nil1))));

    let b: List1 = Cons1(1, Box::new(a));
    // let c: List1 = Cons(2,Box::new(a));  // 发生借用

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

    // Rc<T>
    let a = Rc::new(Cons2(1,
        Rc::new(Cons2(2,
            Rc::new(Nil2)))));
    let b = Cons2(3, Rc::clone(&a));
    let c = Cons2(4, Rc::clone(&a));
    println!("{:?}, {:?}, {:?}, {:?}", a, b, c, Rc::strong_count(&a));
}

内部可变性

  • 内部可变模式(interior mutability pattern):不可变类型暴露出可修改其内部值的 API
    • 它是 Rust 的设计模式之一
    • 它允许在支持有不可变引用的前提下对数据进行修改
      • 数据结构中,使用了 unsafe 代码来绕过

RefCell<T>

  • RefCell<T> 类型代表了其持有数据的唯一所有权
    • 只会在运行时检查借用规则,否则引发 panic
    • 运行时检查:
      • 会延迟暴露问题,可能引发生产事故
      • 因借用计数产生性能损失
  • RefCell<T> 只适用于单线程场景
  • 同一数据的持有者只能由一个

其他

  • Ref<T>RefMut<T>,通过 RefCell<T> 访问:在运行时而不是编译时强制借用规则的类型
  • 引用循环(reference cycles):防止内存泄漏发生,Ref<T>RefCell<T> 都可能发生
    • 每个项的引用数量无法变为 0,该值不能被释放
    • 防止方法:
      • 开发者保证
      • 通过重组数据结构规避,如将 Rc<T> 换成 Weak<T>
  • Cell<T> 通过复制来访问数据
  • Mutex<T> 实现跨线程情形的内部可变性模式
Home Archives Categories Tags Statistics
本文总阅读量 次 本站总访问量 次 本站总访客数