Skip to content

Rust 内存布局

memory layout of rust's data type > What's in the box?

借用规则

  1. 任意时刻,只能拥有一个可变引用或任意个不可变引用(可变与不可变不能同时存在)
  2. 引用必须总是有效

借用类型

borrow() 返回 Ref<T> 类型的智能指针; borrow_mut() 返回 RefMut<T> 类型的智能指针; 两者都实现了 Deref 可作为常规引用。

线程安全类型

Send 允许在线程间传递所有权,Sync 允许多线程访问 如果 &TSend 的话,T 就是 Sync,其引用可以安全地发送到另一个线程

智能指针

Box

使用栈空间需要在编译时知道数据大小,Box 提供在栈空间固定的大小(1 个机器字),指向在堆分配的空间

let t: Box<dyn Vehicle>;
t = Box::new(Truck);
使用 Pin 固定

std::pin - Rust > pin_project - Rust > Pin and suffering

  • Pin<P> 阻止所包装的指针指向的数据被移动
  • PinUnpin 只影响被指向的数据 P::Target

构建一个 Pin<Box<T>> ,如果 T 未实现 Unpin ,那么 x 会被固定在内存中无法移动

pub fn pin(x: T) -> Pin<Box<T, Global>>

Box::pin(x)
// is the same as
Box::into_pin(Box::new(x))

如果 Self 满足 Unpin ,从 Pin<&mut Self> 会隐式转换到 &mut Self

在成员不满足 Unpin 的情况下,从 Pin<&mut Self> 转换到 &mut Self

// 获取不满足 `Unpin` 的成员
let (mut sleep, reader) = unsafe {
    let this = self.get_unchecked_mut();
    (
        Pin::new_unchecked(&mut this.sleep),
        Pin::new_unchecked(&mut this.reader),
    )
}

// 使用 `pin_project`
#[pin_project]
struct SlowRead<R> {
    #[pin]
    reader: R,
    #[pin]
    sleep: Sleep,
}
// 调用 `project` 获取自动生成的结果
let mut this = self.project();

在 receiver 不满足 Unpin 的情况下,创建 Pin,需要确保 pin 之后不再使用 unpin

let mut f = SlowRead::new();
// 创建自身的 `pin`,并 shadow 自身的 `unpin`
let mut f = unsafe { Pin::new_unchecked(&mut f) };
f.read_exact(&mut buf).await?;

// 使用 `pin_utils`
pin_utils::pin_mut!(f);

Rc

允许对同一数据创造多个只读引用,并在最后一个引用离开作用域时释放数据的内存。

Rc 未实现 SendSync,因为多个线程同时访问或修改引用计数会导致数据竞争;可使用 Arc 替代

Cell

RefCell

在运行时检查借用规则,提供内部可变性

impl Messenger for MockMessenger {
    fn send(&self, messenger: &str) {
        self.sent_messenges.borrow_mut().push(String::from(messenger));
    }
}

Trait object

  • trait object 是储存数据指针 (data pointer) 与虚表指针 (vtable pointer) 的胖指针 (fat pointer);
  • data pointer 指向原数据;
  • vtable pointer 指向实现某种 trait 的 vtable,vtable 在编译时生成,共享给相同类型的所有 object;
  • 在进行类型转换时,通过在原指针后添加相应 vtable pointer 形成胖指针。

trait object 是动态大小的类型,必须用引用或其它指针获取

let mut buffer: Vec<u8> = vec![];
let w: &mut dyn Write = &mut buffer;
let mut buffer: Vec<u8> = vec![];
let mut w: Box<dyn Write> = Box::new(buffer);

闭包类型

闭包会通过与外部数据的交互实现不同的 trait

FnOnce

闭包获取了外部数据的所有权,且只可调用一次。数据在调用过程中被移动或释放。

struct MyClosure {
    name: String
}

impl FnOnce for MyClosure {
    fn call_once(self) {
        drop(self.name)
    }
}

FnMut

闭包获取了外部数据的可变引用。

闭包需要是 mut,因为需要获取自身的可变引用。

let mut i: i32 = 0;
let mut f = || {
    i += 1;
}
struct MyClosure {
    i: &mut i32,
}

impl FnMut for MyClosure {
    fn call_mut(&mut self) {
        *self.i += 1;
    }
}

Fn

闭包获取了外部数据的只读引用。

struct MyClosure {
    i: &String,
}

impl Fn for MyClosure {
    fn call(&self) {
        println!("{}", self.msg);
    }
}

move 关键字

move - Rust > Closures: Anonymous Functions that Capture Their Environment

在闭包内,将通过引用或可变引用获取的变量转化为由值获取的变量。

如果闭包内使用了数据的引用,但闭包与引用数据的生命周期不同,使用 move 来明确获取数据所有权。

fn create_closure() -> impl Fn() {
    let msg = String::from("hello");
    let v: Vec<i32> = vec![1, 2];

    move || {
        println!("{}", msg);
        println!("{:?}", v);
    }
}
struct MyClosure {
    msg: String,
    v: Vec<i32>,
}

impl Fn for MyClosure {
    fn(&self) {
        println!("{}", self.msg);
        println!("{:?}", self.v);
    }
}