一、PIN和Safe
其实PIN这个东西,和Safe你说关系大也不多大,你说小也不多小。其实Rust语言搞得声势这么大,就出来一个问题,很多小细节和它所说的一些安全是无法自然衔接的(请注意,是自然)。那么为了解决这些问题,就必须出现 一系列的各种小技巧或者说小办法,这个Pin其实就是其中的一类。
在c++或者其它一些语言中,是有深拷贝和浅拷贝之说的,在Linux中有Cow一说,这些内存处理的情形,或多或少和这个Pin有一些相通之处。在Rust中,很多情况下,其实Pin是用不到的。因为大多数情形下,都实现Unpin Traits,也就是说,对是否使用Pin都无所顾忌。但是在引入的async/await编程中,出现了一类问题,Futrue的移动,导致内部的指针的指针内容的不一致,会产生一些异想不到的后果,这时候Pin就可以上场了。
那么这样来看,其实Pin还是用来Safe操作代码的一种补救措施。或者说,可以认为是Safe动作的一种额外的处理方式。
二、应用场景
在上面其实已经提到了应用场景,就是在移动数据时,内部有数据指针的情况。这里典型就是异步操作时的数据结构的移动。Pin的作用就是固定住指针指针向的内存,在指针被移动时,保证指针指向的内存地址的一致性。这样说可能不好理解,看一个图就明白了:
对Unpin取反,!Unpin的双重否定就是pin。如果一个类型中包含了PhantomPinned,那么这个类型就是!Unpin
其实有Pin就会有UnPin,就会有!Unpin(双重否定就是Pin),Pin的形式如Pin<P>作用就是将指针P指向的内存T锁死,不能移动。即无法使用safe代码获得&mut T。那么Unpin就恰好相反了。而!Unpin其实就是使用了PhantomPinned,它可以实现Unpin变为Pinned。
三、实例分析
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 {
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());
//重点看一下这个交换后打印的动作
std::mem::swap(&mut test1, &mut test2);
println!("a: {}, b: {}", test2.a(), test2.b());
}
运行结果是:
a: test1, b: test1
a: test1, b: test2 -----这里是不是和想象的有所不同?
如果一些函数调用中需要对Pin进行转换有如下两种方式(代码自令狐一冲的知乎:https://zhuanlan.zhihu.com/p/157348723):
use pin_utils::pin_mut; // `pin_utils` is a handy crate available on crates.io
// A function which takes a `Future` that implements `Unpin`.
fn execute_unpin_future(x: impl Future<Output = ()> + Unpin) { /* ... */ }
let fut = async { /* ... */ };
execute_unpin_future(fut); // Error: `fut` does not implement `Unpin` trait
// Pinning with `Box`:使用Box
let fut = async { /* ... */ };
let fut = Box::pin(fut);
execute_unpin_future(fut); // OK
// Pinning with `pin_mut!`:使用这个宏
let fut = async { /* ... */ };
pin_mut!(fut);
execute_unpin_future(fut); // OK
需要说明的是,在栈上Pin是不推荐的,有风险的,这个应该大家都明白。而固定到堆上没有什么问题的,不过需要注意生命周期的过程管理。一般来说,除了Future::poll中使用,不建议在其它方面使用这个Pin,就当不存在吧。当然,也可以使用Drop关键字来强制处理Pin的数据,但实际上这违反了Pin的约束,有点小复杂了。
四、总结
Rust能否取代c++,还需要继续看,不过在一些领域Rust确实是有它的优势的,开放包容,兼容并蓄,才是一个真正的技术者的心态。保守和固步自封,或者