為了搞懂Rust Pin在做什麼,耗費了很多精力,還真是有夠難懂的
About Self-Reference Type 有個資料結構, 其中有個指標指向結構自己或是結構中的某個欄位 例如
1 2 3 4 5 6 7 8 9 struct Test {protected : std ::string a_; const std ::string * b_; public : Test(std ::string text) : a_(std ::move(text)), b_(&a_) {} const std ::string & a () const { return a_; } const std ::string & b () const { return *b_; } };
這裡的b_指向a_的地址, 同樣的事情在Rust寫成這樣
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 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 { unsafe {&*(self .b)} } }
不看Rust的safe機制造成的不同,原理是相同的 現在的問題是,假設物件被移動了,指向結構中某部分的指標該怎麼辦 例如
1 std ::swap(test1, test2);
先從我比較熟悉的C++來說好了
Solution1: Keep invariant 雖然達成目標的方法有很多,不過原則都是一樣:維持不變量就好了
1 2 3 4 void swap (Test& lhs, Test& rhs) { std ::swap(lhs.a_, rhs.a_); } swap(test1, test2);
很顯然,這個方法不適用於Rust
Solution2: Don’t move the object 所謂的Pin也就是這麼一回事,當物件停留在記憶體的某個位置之後,就不會再移動了,所以Self-Reference Type
的物件,在生命週期結束之前,所有的pointer和reference都會有效 在C++禁止的方法也不只一種,這是方法之一
1 2 3 4 5 6 7 8 9 10 11 12 template <typename T>void swap (T&, T&) {}struct Test {protected : std ::string a_; const std ::string * b_; public : Test(std ::string text) : a_(std ::move(text)), b_(&a_) {} friend void swap (Test&, Test&) = delete ; const std ::string & a () const { return a_; } const std ::string & b () const { return *b_; } };
不過由於Rust講究Safety,所以訂了一堆規則
About Pin in Rust 在Rust中對Self-Reference Type的處理,我們要禁止的只有這件事
1 2 3 4 5 6 7 pub fn swap <T>(x: &mut T, y: &mut T) { unsafe { ptr::swap_nonoverlapping_one(x, y); } }
禁止Rust拿到&mut T
的Reference,&mut T
ˊ自然是不行,Box<T>
也做不到這件事,所以就是Pin<T>
登場的時候
Rust的Type分成兩類:
Default Type:可以安全在Rust Move的類型
Default Type都實作了auto Unpin
trait,也就是什麼都不用做
Self-Reference Type:也就是上面提到的部分
必須實作!Unpin
的部分
使用PhantomPinned
就可以了
以下是個範例程式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 use std::pin::Pin;use std::marker::PhantomPinned;#[derive(Debug)] struct Test { _marker: PhantomPinned, } impl Test { fn new () -> Self { Test { _marker: PhantomPinned } } } pub fn main () { let mut test1 = Box ::pin(Test::new()); let mut test2 = Box ::pin(Test::new()); std::mem::swap(test1.as_mut().get_mut(), test2.as_mut().get_mut()); }
你把上面的PhantomPinned
註解掉,程式就能正常運作了 Pin還有很多細節,等我真的變成全職Rust工程師在研究吧
Reference