What is Pin and why we need that

the idea behind Pin is the moment you place something in a pin you are promising that you will not move it again. that’s said if you have a Pin, you can’t get mutable reference to the type behind it unless you promise you won’t move it.

为什么需要Pin?

假如我们需要实现一个parser,这个parser从str 解析出token,但我们不希望这个parser和str有引用关系,我们希望是一个ownedParser,这里我们希望Parsed reference buf, 但这里Parsed的lifetime无法指定,因为这里Parsed实际上引用了自己,称为 self referential.

  1. struct OwnedParser {
  2. buf: Vec<u8>,
  3. parsed: Parsed<'lifetimeOfBuf>
  4. }
  5. struct Parsed<'a> {
  6. token: &'a [u8]
  7. }

假设rust允许我们自引用,这里,parsed中包含了一个指向buf的引用,这样会有什么问题吗?

  1. let a = OwnedParser::new("str");
  2. let b = a;

如果 a 发生了move, 会出现什么情况?

Pin - 图1

在之前的文章中介绍过move的本质其实就是一次memcpy,当a move 到b后,buf被copy到新的内存,但是parsed指向的仍然是 a 的buf,这样就造成b的parsed字段所指向的buf是一个非法地址。
What actually Move is

Pin

为了解决或者说绕过这个问题,出现了Pin以及Unpin, Pin是一个struct,Unpin是一个trait

  1. pub struct Pin<P> {
  2. pointer: P,
  3. }
  4. pub auto trait Unpin {}

可以通过Pin::new() 构造一个Pin对象,将目标指针包在Pin中,但实际上Pin并没有魔法可以阻止目标对象发生move或者通过mem::replace替换,它能做的是阻止调用者访问到目标对象 T
image.png

例如这段代码,实际上,你无法防止s发生move

  1. let mut s = "this".to_string();
  2. let mut pinned_string = Pin::new(&mut s);
  3. let s2 = s;

所以tokio提供了一个宏 pin! 用来将原变量覆盖

  1. let mut s = "this".to_string();
  2. tokio::pin!(s);
  3. 这里s就变成了 Pin<&mut String>

然而,看了文档后,我发现即使在pin后,任然可以通过 pin::get_mut() 拿到原变量的mut ref,那不是任然可以在pin之后修改原变量吗?
image.png
仔细看get_mut的参数,发现 T:Unpin, Unpin 又是个什么?

Unpin

Types that can be safely moved after being pinned. This trait is automatically implemented for almost every type.

Unpin是一个autotrait,基本上所有的类型都实现了Unpin,除非你主动实现 !Unpin, 或者你的结构体中有一个字段是 !Unpin, 或者,这个struct中包含 PhantomPinned类型字段。那么什么是一个 !Unpin的类型?
包含一个自引用字段的类型就是!Unpin, 所有的async函数在编译后都会实现!Unpin

image.png
当T是!Unpin类型的时候,就只能通过 get_unchecked_mut 来拿到mut ref,注意这是一个unsafe的函数,也就是说编译器需要你自己保证不会在拿到mut ref后,做一些危险的事情。

Async

那这和async await 有什么关系呢?为什么poll要求 FooFuture pin呢?

  1. struct FooFuture;
  2. impl Future for FooFuture {
  3. type Output = ();
  4. fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
  5. todo!()
  6. }
  7. }

这和async await的实现有关
Pinning

pin_project

当我们需要实现一个future的时候,我们通常会需要包装另一个future,例如

  1. struct MySleep {
  2. sleep: tokio::time::Sleep
  3. }
  4. impl Future for MySleep {
  5. type Output = ();
  6. fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
  7. self.sleep.poll(cx)
  8. }
  9. }
  10. error[E0599]: no method named `poll` found for struct `Sleep` in the current scope
  11. --> src/main.rs:69:20
  12. |
  13. 69 | self.sleep.poll(cx)
  14. | ^^^^ method not found in `Sleep`
  15. |
  16. ::: /Users/sean/.rustup/toolchains/stable-x86_64-apple-darwin/lib/rustlib/src/rust/library/core/src/future/future.rs:99:8

编译失败,并不能直接poll sleep, 因为sleep不是Pin类型的,那直接Pin::new() 试试?

  1. impl Future for MySleep {
  2. type Output = ();
  3. fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
  4. Pin::new(&mut self.sleep).poll(cx)
  5. }
  6. }
  7. error[E0277]: `PhantomPinned` cannot be unpinned
  8. --> src/main.rs:69:9
  9. |
  10. 69 | Pin::new(&mut self.sleep).poll(cx)
  11. | ^^^^^^^^ within `tokio::time::driver::sleep::_::__Origin<'_>`, the trait `Unpin` is not implemented for `PhantomPinned`
  12. |
  13. = note: consider using `Box::pin`
  14. = note: required because it appears within the type `tokio::time::driver::entry::TimerEntry`
  15. = note: required because it appears within the type `tokio::time::driver::sleep::_::__Origin<'_>`
  16. = note: required because of the requirements on the impl of `Unpin` for `Sleep`
  17. note: required by `Pin::<P>::new`

仍然不行,因为Sleep类型中有个PhantomPinned,导致Sleep是!Unpin的,那就只能unsafe了

  1. impl Future for MySleep {
  2. type Output = ();
  3. fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
  4. unsafe {
  5. let this = self.get_unchecked_mut();
  6. Pin::new_unchecked(&mut this.sleep).poll(cx)
  7. }
  8. }
  9. }

这回编译通过了,但显然unsafe让人看着很不舒服,所以通常会用pin_project这个crate来对struct进行封装

  1. #[pin_project]
  2. struct MySleep {
  3. #[pin]
  4. sleep: tokio::time::Sleep
  5. }
  6. impl Future for MySleep {
  7. type Output = ();
  8. fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
  9. let mut this = self.project();
  10. this.sleep.as_mut().poll(cx)
  11. }
  12. }

通过cargo expand将宏展开,可以看到,pin_project生成的代码

cargo expand 需要用rust +nightly 执行

  1. #[allow(unused_extern_crates)]
  2. extern crate pin_project as _pin_project;
  3. #[allow(dead_code)]
  4. #[allow(clippy::mut_mut)]
  5. struct __MySleepProjection<'pin> // 通过project() 返回一个新的 __MySleepProjection 类型
  6. where
  7. MySleep: 'pin, // 这里没看懂,MySleep: 'pin 代表什么意思?
  8. {
  9. sleep: ::pin_project::__private::Pin<&'pin mut (tokio::time::Sleep)>,
  10. }
  11. #[allow(dead_code)]
  12. #[allow(clippy::ref_option_ref)]
  13. struct __MySleepProjectionRef<'pin> // 通过 prject_ref() 返回的类型
  14. where
  15. MySleep: 'pin,
  16. {
  17. sleep: ::pin_project::__private::Pin<&'pin (tokio::time::Sleep)>,
  18. }
  19. impl MySleep {
  20. fn project<'pin>(
  21. self: _pin_project::__private::Pin<&'pin mut Self>,
  22. ) -> __MySleepProjection<'pin> {
  23. unsafe {
  24. let Self { sleep } = self.get_unchecked_mut(); // 通过unchecked_mut() 返回 sleep mut ref
  25. __MySleepProjection {
  26. sleep: _pin_project::__private::Pin::new_unchecked(sleep), // 返回project类型
  27. }
  28. }
  29. }
  30. #[allow(clippy::missing_const_for_fn)]
  31. fn project_ref<'pin>(
  32. self: _pin_project::__private::Pin<&'pin Self>,
  33. ) -> __MySleepProjectionRef<'pin> {
  34. unsafe {
  35. let Self { sleep } = self.get_ref();
  36. __MySleepProjectionRef {
  37. sleep: _pin_project::__private::Pin::new_unchecked(sleep),
  38. }
  39. }
  40. }
  41. }
  42. // Ensure that it's impossible to use pin projections on a #[repr(packed)]
  43. #[forbid(unaligned_references, safe_packed_borrows)]
  44. fn __assert_not_repr_packed(this: &MySleep) {
  45. let _ = &this.sleep;
  46. }
  47. // 这个自动生成的结构体,目的是为了为MySleep自动impl Unpin
  48. // impl<T, U> Unpin for Struct<T, U> where T: Unpin {}
  49. // However, if struct is public and there is a private type field,
  50. // this would cause an E0446 (private type in public interface).
  51. //
  52. // When RFC 2145 is implemented (rust-lang/rust#48054),
  53. // this will become a lint, rather then a hard error.
  54. //
  55. // As a workaround for this, we generate a new struct, containing all of
  56. // the pinned fields from our #[pin_project] type. This struct is declared
  57. // within a function, which makes it impossible to be named by user code.
  58. // This guarantees that it will use the default auto-trait impl for Unpin -
  59. // that is, it will implement Unpin iff all of its fields implement Unpin.
  60. // This type can be safely declared as 'public', satisfying the privacy
  61. // checker without actually allowing user code to access it.
  62. //
  63. // This allows users to apply the #[pin_project] attribute to types
  64. // regardless of the privacy of the types of their fields.
  65. #[allow(missing_debug_implementations)]
  66. struct __MySleep<'pin> {
  67. __pin_project_use_generics: _pin_project::__private::AlwaysUnpin<'pin, ()>,
  68. __field0: tokio::time::Sleep,
  69. }
  70. impl<'pin> _pin_project::__private::Unpin for MySleep where
  71. __MySleep<'pin>: _pin_project::__private::Unpin
  72. {
  73. }
  74. #[doc(hidden)]
  75. unsafe impl<'pin> _pin_project::UnsafeUnpin for MySleep where
  76. __MySleep<'pin>: _pin_project::__private::Unpin
  77. {
  78. }
  79. trait MySleepMustNotImplDrop {}
  80. #[allow(clippy::drop_bounds, drop_bounds)]
  81. impl<T: _pin_project::__private::Drop> MySleepMustNotImplDrop for T {}
  82. impl MySleepMustNotImplDrop for MySleep {}
  83. #[doc(hidden)]
  84. impl _pin_project::__private::PinnedDrop for MySleep {
  85. unsafe fn drop(self: _pin_project::__private::Pin<&mut Self>) {}
  86. }

Pin>

Box is a pointer, it makes sure that it has a stable location, and pin makes sure that you will never be able to move T unless it’s unpin (you can never get mutable reference to T), Pin<Box> is a way to get things that are !Unpin to Unpin.

也可以通过Box将sleep包起来,因为无论T是否是Unpin,Box 一定是Unpin的。

  1. struct MySleep {
  2. sleep: Pin<Box<tokio::time::Sleep>>
  3. }

总结

Pin的作用实际上并不是把内存pin住,而是不让你轻松的拿到 &mut T, 这样你就不能去替换或者move T,仅此而已