译者注: Pin是在使用Future时一个非常重要的概念,我的理解是: 通过使用Pin,让用户无法安全的获取到 &mut T,进而无法进行上述例子中的swap. 如果你觉得你的和这个struct没有自引用的问题,你可以自己实现UnPin.
Future的时候为什么需要PinPin类型和Unpin标记组成(UnPin是Rust中为数不多的几个auto trait). Pin存在的目的就是为了让那些实现了!UnPin的类型遵守特定的规则.!Unpin 的意思是“not-un-pin”。这个命名方案是 Rusts 的安全特性之一,它故意测试您是否因为太累而无法安全地使用这个标记来实现类型。如果你因为 UnPin开始感到困惑,或者甚至生气,那么你就应该这样做!是时候放下工作,以全新的心态重新开始明天的生活了,这是一个好兆头。
Unpin 和!UnPin ,使他们更容易理解。use std::pin::Pin;#[derive(Debug)]struct Test {a: String,b: *const String,}impl Test {fn new(txt: &str) -> Self {let a = String::from(txt);Test {a,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 {unsafe {&*(self.b)}}}
Test。Test需要创建一个init方法,这个方法很奇怪,但是为了尽可能简短,我们需要这个方法。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: test1a: 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());std::mem::swap(&mut test1, &mut test2);println!("a: {}, b: {}", test2.a(), test2.b());}
a: test1, b: test1a: test1, b: test1
但是实际上我们得到的是:
a: test1, b: test1a: test1, 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());std::mem::swap(&mut test1, &mut test2);test1.a = "I've totally changed now!".to_string();println!("a: {}, b: {}", test2.a(), test2.b());}

Pin来解决这个问题。让我们来看看我们的例子是什么样的: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 {let a = String::from(txt);Test {a,b: std::ptr::null(),// This makes our type `!Unpin`_marker: PhantomPinned,}}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 {unsafe { &*(self.b) }}}
!UnPin,那么它将总是unsafe。unsafe,我们需要将数据钉在堆上,我们马上就会展示这一点。pub fn main() {// test1 is safe to move before we initialize itlet mut test1 = Test::new("test1");// Notice how we shadow `test1` to prevent it from beeing accessed againlet 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()));println!("a: {}, b: {}", Test::a(test2.as_ref()), Test::b(test2.as_ref()));}
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()));}
需要注意的是,栈pinning总是依赖于我们所在的当前栈帧,因此我们不能在一个栈帧中创建一个自引用对象并返回它,因为任何指向“self”的指针都是无效的。
如果你把一个值固定在一个栈上,这也会让你承担很多责任。一个很容易犯的错误是,忘记对原始变量进行阴影处理,因为这样可以在初始化后drop固定的指针并访问原来的值:fn main() {
let mut test1 = Test::new("test1");
let mut test1_pin = unsafe { Pin::new_unchecked(&mut test1) };
Test::init(test1_pin.as_mut());
drop(test1_pin);
let mut test2 = Test::new("test2");
mem::swap(&mut test1, &mut test2);
println!("Not self referential anymore: {:?}", test1.b);
}
固定在堆上
init方法。固定到堆是安全的,这样用户不需要实现任何不安全的代码: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) -> Pin<Box<Self>> {let a = String::from(txt);let t = Test {a,b: std::ptr::null(),_marker: PhantomPinned,};let mut boxed = Box::pin(t);let self_ptr: *const String = &boxed.as_ref().a;unsafe { boxed.as_mut().get_unchecked_mut().b = self_ptr };boxed}fn a<'a>(self: Pin<&'a Self>) -> &'a str {&self.get_ref().a}fn b<'a>(self: Pin<&'a Self>) -> &'a String {unsafe { &*(self.b) }}}pub fn main() {let mut test1 = Test::new("test1");let mut test2 = Test::new("test2");println!("a: {}, b: {}",test1.as_ref().a(), test1.as_ref().b());println!("a: {}, b: {}",test2.as_ref().a(), test2.as_ref().b());}
!Unpin有意义,固定一个堆分配的值也是安全的。一旦在堆上分配了数据,它就会有一个稳定的地址。T:UnPin(这是默认值),Pin<'a,T>完全定价与&'a mut T. 换句话说: UnPin意味着这个类型即使在固定时也可以移动,所以Pin对这个类型没有影响。T:!UnPin,从Pin< T>获取到&mut T,则必须使用unsafe. 换句话说,!Unpin能够阻止API的使用者移动T,除非他写出unsafe的代码.Future和Generators是两个例外。!UnPin这些类型的实现很有可能是不安全的. 在这种类型被钉住后移动它可能会导致程序崩溃。在撰写本书时,创建和读取自引用结构的字段仍然需要不安全的方法(唯一的方法是创建一个包含指向自身的原始指针的结构)。!UnPin. 当使用stable版本时,可以将std: : marker: : PhantomPinned 添加到类型上。!UnPin的指向栈上的指针固定需要unsafe.!UnPin的指向堆上的指针固定,不需要unsafe,可以直接使用Box::Pin.不安全的代码并不意味着它真的“unsafe” ,它只是减轻了通常从编译器得到的保证。一个不安全的实现可能是完全安全的,但是您没有编译器保证的安全网。
Futures的时候,这正是我们要做的,我们很快就完成了。#![feature(optin_builtin_traits, negative_impls)] // needed to implement `!Unpin`use std::pin::Pin;pub fn main() {let gen1 = GeneratorA::start();let gen2 = GeneratorA::start();// Before we pin the pointers, this is safe to do// std::mem::swap(&mut gen, &mut gen2);// constructing a `Pin::new()` on a type which does not implement `Unpin` is// unsafe. A value pinned to heap can be constructed while staying in safe// Rust so we can use that to avoid unsafe. You can also use crates like// `pin_utils` to pin to the stack safely, just remember that they use// unsafe under the hood so it's like using an already-reviewed unsafe// implementation.let mut pinned1 = Box::pin(gen1);let mut pinned2 = Box::pin(gen2);// Uncomment these if you think it's safe to pin the values to the stack instead// (it is in this case). Remember to comment out the two previous lines first.//let mut pinned1 = unsafe { Pin::new_unchecked(&mut gen1) };//let mut pinned2 = unsafe { Pin::new_unchecked(&mut gen2) };if let GeneratorState::Yielded(n) = pinned1.as_mut().resume() {println!("Gen1 got value {}", n);}if let GeneratorState::Yielded(n) = pinned2.as_mut().resume() {println!("Gen2 got value {}", n);};// This won't work:// std::mem::swap(&mut gen, &mut gen2);// This will work but will just swap the pointers so nothing bad happens here:// std::mem::swap(&mut pinned1, &mut pinned2);let _ = pinned1.as_mut().resume();let _ = pinned2.as_mut().resume();}enum GeneratorState<Y, R> {Yielded(Y),Complete(R),}trait Generator {type Yield;type Return;fn resume(self: Pin<&mut Self>) -> GeneratorState<Self::Yield, Self::Return>;}enum GeneratorA {Enter,Yield1 {to_borrow: String,borrowed: *const String,},Exit,}impl GeneratorA {fn start() -> Self {GeneratorA::Enter}}// This tells us that the underlying pointer is not safe to move after pinning.// In this case, only we as implementors "feel" this, however, if someone is// relying on our Pinned pointer this will prevent them from moving it. You need// to enable the feature flag ` #![feature(optin_builtin_traits)]` and use the// nightly compiler to implement `!Unpin`. Normally, you would use// `std::marker::PhantomPinned` to indicate that the struct is `!Unpin`.impl !Unpin for GeneratorA { }impl Generator for GeneratorA {type Yield = usize;type Return = ();fn resume(self: Pin<&mut Self>) -> GeneratorState<Self::Yield, Self::Return> {// lets us get ownership over current statelet this = unsafe { self.get_unchecked_mut() };match this {GeneratorA::Enter => {let to_borrow = String::from("Hello");let borrowed = &to_borrow;let res = borrowed.len();*this = GeneratorA::Yield1 {to_borrow, borrowed: std::ptr::null()};// Trick to actually get a self reference. We can't reference// the `String` earlier since these references will point to the// location in this stack frame which will not be valid anymore// when this function returns.if let GeneratorA::Yield1 {to_borrow, borrowed} = this {*borrowed = to_borrow;}GeneratorState::Yielded(res)}GeneratorA::Yield1 {borrowed, ..} => {let borrowed: &String = unsafe {&**borrowed};println!("{} world", borrowed);*this = GeneratorA::Exit;GeneratorState::Complete(())}GeneratorA::Exit => panic!("Can't advance an exited generator!"),}}}
unafe然后把值固定到栈上。用户知道如果他们事后移动了这个值,那么他们在就违反了当他们使用unsafe时候做出的承诺,也就是一直持有.yield或者await关键词时会发生什么,以及如果我们想要安全地跨yield/await借用时。,为什么我们需要Pin。原文作者:Rust语言中文社区;如有侵权,请联系管理员删除。