深入理解Rust的Pin和Unpin:理论与实践解析在Rust的异步编程中,Pin和Unpin是两个核心概念,它们决定了对象是否可以在内存中移动。本篇文章将深入探讨Pin的工作原理及其背后的设计逻辑,帮助读者更好地理解和使用这些工具以编写更安全和高效的代码。Pin和U
在 Rust 的异步编程中,Pin 和 Unpin 是两个核心概念,它们决定了对象是否可以在内存中移动。本篇文章将深入探讨 Pin 的工作原理及其背后的设计逻辑,帮助读者更好地理解和使用这些工具以编写更安全和高效的代码。
Pin 和 Unpin 是 Rust 中与内存安全密切相关的特性。通过 Pin,可以将对象固定在内存中的特定位置,防止移动可能导致的引用失效问题。而 Unpin 则表示对象可以安全移动。本篇文章首先分析了异步代码生成的 Future 的内部结构,然后深入讲解了 Pin 的原理、Unpin 特性及其实践应用,包括在堆上固定对象、标记类型 PhantomPinned 的作用,以及如何在实际代码中避免移动敏感数据。此外,文章通过详细代码示例演示了 Pin 和 Unpin 的实际使用场景。
let fut_one = /* ... */; // Future 1
let fut_two = /* ... */; // Future 2
async move {
fut_one.await;
fut_two.await;
}
// 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
的引用就变成了不合法的。
#[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(...
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!