Rust

2025年11月03日更新 13 人订阅
原价: ¥ 6 限时优惠
专栏简介

深入理解 Rust 的 Pin 和 Unpin:理论与实践解析

深入理解Rust的Pin和Unpin:理论与实践解析在Rust的异步编程中,Pin和Unpin是两个核心概念,它们决定了对象是否可以在内存中移动。本篇文章将深入探讨Pin的工作原理及其背后的设计逻辑,帮助读者更好地理解和使用这些工具以编写更安全和高效的代码。Pin和U

深入理解 Rust 的 Pin 和 Unpin:理论与实践解析

在 Rust 的异步编程中,Pin 和 Unpin 是两个核心概念,它们决定了对象是否可以在内存中移动。本篇文章将深入探讨 Pin 的工作原理及其背后的设计逻辑,帮助读者更好地理解和使用这些工具以编写更安全和高效的代码。

Pin 和 Unpin 是 Rust 中与内存安全密切相关的特性。通过 Pin,可以将对象固定在内存中的特定位置,防止移动可能导致的引用失效问题。而 Unpin 则表示对象可以安全移动。本篇文章首先分析了异步代码生成的 Future 的内部结构,然后深入讲解了 Pin 的原理、Unpin 特性及其实践应用,包括在堆上固定对象、标记类型 PhantomPinned 的作用,以及如何在实际代码中避免移动敏感数据。此外,文章通过详细代码示例演示了 Pin 和 Unpin 的实际使用场景。

Pinning

什么是 Pin

  • Pin 与 Unpin 标记一起工作
  • Pin 会保证实现了 !Unpin 的对象永远不会被移动

为什么需要 Pin?

let fut_one = /* ... */; // Future 1
let fut_two = /* ... */; // Future 2
async move {
    fut_one.await;
    fut_two.await;
}
  • 这会创建一个实现了 Future trait 的匿名类型
  • 提供一个和下面代码类似的 poll 方法
// The `Future` type generated by our `async { ... }` block
// `async { ... }`语句块创建的 `Future` 类型
struct AsyncFuture {
    fut_one: FutOne,
    fut_two: FutTwo,
    state: State,
}

// List of states our `async` block can be in
// `async` 语句块可能处于的状态
enum State {
    AwaitingFutOne,
    AwaitingFutTwo,
    Done,
}

impl Future for AsyncFuture {
    type Output = ();

    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
        loop {
            match self.state {
                State::AwaitingFutOne => match self.fut_one.poll(..) {
                    Poll::Ready(()) => self.state = State::AwaitingFutTwo,
                    Poll::Pending => return Poll::Pending,
                }
                State::AwaitingFutTwo => match self.fut_two.poll(..) {
                    Poll::Ready(()) => self.state = State::Done,
                    Poll::Pending => return Poll::Pending,
                }
                State::Done => return Poll::Ready(()),
            }
        }
    }
}

poll 第一次被调用时,它会去查询 fut_one 的状态,若 fut_one 无法完成,则 poll 方法会返回。未来对 poll 的调用将从上一次调用结束的地方开始。该过程会一直持续,直到 Future 完成为止。

如果上例中 async 块使用引用,会如何?

async {
    let mut x = [0; 128];
    let read_into_buf_fut = read_into_buf(&mut x);
    read_into_buf_fut.await;
    println!("{:?}", x);
}

这段代码会编译成下面的形式:

struct ReadIntoBuf<'a> {
    buf: &'a mut [u8], // 指向下面的`x`字段
}

struct AsyncFuture {
    x: [u8; 128],
    read_into_buf_fut: ReadIntoBuf<'what_lifetime?>,
}

这里,ReadIntoBuf 拥有一个引用字段,指向了结构体的另一个字段 x ,一旦 AsyncFuture 被移动,那 x 的地址也将随之变化,此时对 x 的引用就变成了不合法的。

  • 把 Future Pin(钉)到内存中的特定位置会防止该问题的发生:
    • 可以在 async 块里安全的创建到值的引用

Pin 介绍

#[derive(Debug)]
struct Test {
    a: String,
    b: *const String,
}

impl Test {
    fn new(txt: &str) -> Self {
        Test {
            a: String::from(txt),
            b: std::ptr::null(),
        }
    }

    fn init(&mut self) {
        let self_ref: *const String = &self.a;
        self.b = self_ref;
    }

    fn a(&self) -> &str {
        &self.a
    }

    fn b(&self) -> &String {
        assert!(!self.b.is_null(), "Test::b called without Test::init being called first");
        unsafe { &*(self.b) }
    }
}

fn main() {
    let mut test1 = Test::new("test1");
    test1.init();
    let mut test2 = Test::new("test2");
    test2.init();

    println!("a: {}, b: {}", test1.a(), test1.b());
    println!("a: {}, b: {}", test2.a(), test2.b());

}

运行输出

a: test1, b: test1
a: test2, b: test2

修改之后


fn main() {
    let mut test1 = Test::new("test1");
    test1.init();
    let mut test2 = Test::new("test2");
    test2.init();

    println!("a: {}, b: {}", test1.a(), test1.b(...

剩余55%的内容订阅专栏后可查看

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论