0%

Rust Pin

为什么需要 Pin

假如我们有一个对象定义如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
use std::pin::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) }
}
}

常规情况下

1
2
3
4
5
6
7
8
9
10
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());

}

输出是

1
2
a: test1, b: test1
a: test2, b: test2

我们加上一个 swap 呢?

1
2
3
4
5
6
7
8
9
10
11
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());
std::mem::swap(&mut test1, &mut test2);
println!("a: {}, b: {}", test2.a(), test2.b());

}

结果答案就变成了

1
2
a: test1, b: test1
a: test1, b: test2

原因就是因为我们将对象的指针交换了,但是并没有保证内部引用 b 还是指向当前的自己。

解决之道

显然我们需要保证 b 所指向的 Self 并不能被修改,因此 Pin 可以这么使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
use std::pin::Pin;
use std::marker::PhantomPinned;

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


impl Test {
fn new(txt: &str) -> Self {
Test {
a: String::from(txt),
b: std::ptr::null(),
_marker: PhantomPinned, // This makes our type `!Unpin`
}
}
fn init<'a>(self: Pin<&'a mut Self>) {
let self_ptr: *const String = &self.a;
let this = unsafe { self.get_unchecked_mut() };
this.b = self_ptr;
}

fn a<'a>(self: Pin<&'a Self>) -> &'a str {
&self.get_ref().a
}

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

我们此时使用之前的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
pub fn main() {
let mut test1 = Test::new("test1");
let mut test1 = unsafe { Pin::new_unchecked(&mut test1) };
Test::init(test1.as_mut());

let mut test2 = Test::new("test2");
let mut test2 = unsafe { Pin::new_unchecked(&mut test2) };
Test::init(test2.as_mut());

println!("a: {}, b: {}", Test::a(test1.as_ref()), Test::b(test1.as_ref()));
std::mem::swap(test1.get_mut(), test2.get_mut());
println!("a: {}, b: {}", Test::a(test2.as_ref()), Test::b(test2.as_ref()));
}

会输出如下错误

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/Users/yanick/.cargo/bin/cargo build --color=always --message-format=json-diagnostic-rendered-ansi --package helloworld --bin helloworld
Compiling helloworld v0.1.0 (/Users/yanick/Codes/rust/helloworld)
error[E0277]: `PhantomPinned` cannot be unpinned
--> src/main.rs:46:26
|
46 | std::mem::swap(test1.get_mut(), test2.get_mut());
| ^^^^^^^ within `Test`, the trait `Unpin` is not implemented for `PhantomPinned`
|
= note: required because it appears within the type `Test`

error[E0277]: `PhantomPinned` cannot be unpinned
--> src/main.rs:46:43
|
46 | std::mem::swap(test1.get_mut(), test2.get_mut());
| ^^^^^^^ within `Test`, the trait `Unpin` is not implemented for `PhantomPinned`
|
= note: required because it appears within the type `Test`

error: aborting due to 2 previous errors

For more information about this error, try `rustc --explain E0277`.
error: could not compile `helloworld`
To learn more, run the command again with --verbose.
Process finished with exit code 101

从编译时期保证我们的指针不能够被移动。

Pin in Asynchronous

加入我们有个 两个 funture 任务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
struct AsyncFuture {
fut_one: FutOne,
fut_two: FutTwo,
state: State,
}

// List of states our `async` block can be in
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(()),
}
}
}
}

如果在执行 fut_one.poll 时候并没有就绪,显然我们会继续执行 fut_two

如果 async 中包含的执行体是这样的

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

在编译的时候,会进行转为为如下格式

1
2
3
4
5
6
7
8
struct ReadIntoBuf<'a> {
buf: &'a mut [u8], // points to `x` below
}

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

显然 ReadIntoBuf 持有了 AsyncFunture 中的一个变量,如果 Move AsyncFuture 就会导致 x 失效。因此我们需要保证 AsyncFuture 不会被 Move(值得注意的这是一个编译期的保证)

poll 函数定义中,我们可以发现定义如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#[stable(feature = "futures_api", since = "1.36.0")]
impl<F: ?Sized + Future + Unpin> Future for &mut F {
type Output = F::Output;

fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
F::poll(Pin::new(&mut **self), cx)
}
}

#[stable(feature = "futures_api", since = "1.36.0")]
impl<P> Future for Pin<P>
where
P: Unpin + ops::DerefMut<Target: Future>,
{
type Output = <<P as ops::Deref>::Target as Future>::Output;

fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
Pin::get_mut(self).as_mut().poll(cx)
}
}

如果不是一个 Unpin 的对象,我们就创建一个 PinPoll,最终都需要接受一个被 Pin 的任务来执行。

参考文档

来杯奶茶, 嗝~~~