一、Pin由来
在Rust中,自引用结构会导致,此变量被move后,其内部自引用的指针指向不改变,从而存在安全隐患 。
注意:Pin是一个struct。UnPin和!UnPin是trait。这个要分清。
二、方案
对原始的自引用结构,如何增加安全性,方案有以下两步:
1、里面加Phantompinned,
2、外面套上Pin,
这样新的结构被move后,可以保证里面的自引用的指针指向的内容正确。
基于以上分析,我们来进行相关验证:
1、验证普通的结构体move前后行为:move的行为会产生什么影响?
2、带自引用结构move前后的行为:是否存在安全隐患?
3、带PhantomPinned自引用结构move前后的行为:是否解决了原先产生的问题?
在行为方面,具体的考察在于指针地址,指针指向内容的变化,是否能揭示相关问题。
三、相关代码
因为不涉及外部库,cargo.toml文件不需要另列。main.rs代码如下:
bash
use std::pin::Pin;
use std::marker::PhantomPinned;
// unpin结构,没有自引用
#[derive(Debug,Default)]
struct Free{
content: String,
}
// unpin结构 =>move后,ptr对应的指针还会指向原来的地址,但不能正确指向content内容
#[derive(Debug)]
struct SelfRef {
content: String,
_ptr: *const String,//对应content 或者是&String
}
impl SelfRef{
fn default() ->Self{
SelfRef{
content: String::from("hello world!"),
_ptr: std::ptr::null(),
}
}
fn set_ref_value(&mut self){
self._ptr = & self.content as *const String;
}
}
// !unpin结构,通过引入phantompinned,再pin后,SelfRefPinned被move后,_ptr对应指针可以正确指向content
#[derive(Debug)]
struct SelfRefPinned {
content: String,
_ptr: *const String,//对应content
_marker: PhantomPinned,
}
impl SelfRefPinned{
fn default() ->Self{
SelfRefPinned{
content: String::from("hello world!"),
_ptr: std::ptr::null(),
_marker: PhantomPinned,
}
}
fn set_ref_value(pinned_obj: Pin<&mut Self>){
let content_ptr = &pinned_obj.content as *const String;
let mut_self_ref: &mut SelfRefPinned = unsafe { pinned_obj.get_unchecked_mut() };
mut_self_ref._ptr = content_ptr;
}
}
fn main() {
let _free = Free::default();
let mut _self_ref = SelfRef::default();
_self_ref.set_ref_value();
let mut self_ref_pin = SelfRefPinned::default();
let mut _self_ref_pinned = unsafe { Pin::new_unchecked(&mut self_ref_pin) };
SelfRefPinned::set_ref_value(_self_ref_pinned.as_mut());
// Free before move
println!("-----------before move-------------");
println!("_free content:{:?} content 内存指针: {:p}",_free.content,&_free.content);
// Free after move
println!("-----------after move-------------");
let free_ = _free; //第1次move操作
println!("free_ content:{:?} content 内存指针: {:p}",free_.content,&free_.content);
// SelfRef before move
println!("-----------before move-------------");
println!("_self_ref 内存地址:{:p} content内存地址:{:p}",&_self_ref,&_self_ref.content);
println!("_self_ref._ptr对应value :{:?} 自引用指针的内存地址: {:?}",unsafe{&*_self_ref._ptr},_self_ref._ptr);
// SelfRef after move
println!("-----------after move-------------");
let self_ref_ = _self_ref; //第1次move操作
println!("self_ref_ 内存地址:{:p} content内存地址:{:p}",&self_ref_,&self_ref_.content);
println!("self_ref_._ptr对应value :{:?} 自引用指针的内存地址: {:?}",unsafe{&*self_ref_._ptr},self_ref_._ptr);
// SelfRef move again
println!("-----------after move again -------------");
let _self_ref_ = self_ref_; //第2次 move操作
println!("_self_ref_ 内存地址:{:p} content内存地址:{:p}",&_self_ref_,&_self_ref_.content);
println!("_self_ref_._ptr对应value :{:?} 自引用指针的内存地址: {:?}",unsafe{&*_self_ref_._ptr},_self_ref_._ptr);
// SelfRefPinned before move
println!("-----------before move-------------");
println!("_self_ref_pinned 内存地址:{:p} content内存地址:{:p}",&_self_ref_pinned,&_self_ref_pinned.content);
println!("_self_ref_pinned._ptr对应value :{:?} 自引用指针的内存地址: {:?}",unsafe{&*_self_ref_pinned._ptr},_self_ref_pinned._ptr);
// SelfRefPinned after move
println!("-----------after move-------------");
let self_ref_pinned_ = _self_ref_pinned; //第1次move 操作
println!("self_ref_pinned_ 内存地址:{:p} content内存地址:{:p}",&self_ref_pinned_,&self_ref_pinned_.content);
println!("self_ref_pinned_._ptr对应value :{:?} 自引用指针的内存地址: {:?}",unsafe{&*self_ref_pinned_._ptr},self_ref_pinned_._ptr);
}
四、输出
bash
-----------before move-------------
_free content:"" content 内存指针: 0x7ffd840875f0
-----------after move-------------
free_ content:"" content 内存指针: 0x7ffd840875d0
-----------before move-------------
_self_ref 内存地址:0x7ffd84087570 content内存地址:0x7ffd84087570
_self_ref._ptr对应value :"hello world!" 自引用指针的内存地址: 0x7ffd84087570
-----------after move-------------
self_ref_ 内存地址:0x7ffd84087590 content内存地址:0x7ffd84087590
self_ref_._ptr对应value :"hello world!" 自引用指针的内存地址: 0x7ffd84087570
-----------after move again -------------
_self_ref_ 内存地址:0x7ffd84087540 content内存地址:0x7ffd84087540
_self_ref_._ptr对应value :"hello world!" 自引用指针的内存地址: 0x7ffd84087570
-----------before move-------------
_self_ref_pinned 内存地址:0x7ffd84087530 content内存地址:0x7ffd840875b0
_self_ref_pinned._ptr对应value :"hello world!" 自引用指针的内存地址: 0x7ffd840875b0
-----------after move-------------
self_ref_pinned_ 内存地址:0x7ffd84087568 content内存地址:0x7ffd840875b0
self_ref_pinned_._ptr对应value :"hello world!" 自引用指针的内存地址: 0x7ffd840875b0
五、总结
1、几点观察
(1)从上面Free类型来看,move后,struct对象的内存地址有变动。
(2)自引用结构move后,可以看见:尽管对象的struct内存地址发生变化后,但对象中的content的地址也发生了新的变化,但对象中的自引用对应指针并没有变化(还是指向move前的对象的content地址),并没指向新对象content所对应的内存地址。这样,就存在了不一致的问题。
(3)PhantomPinned和Pin之后,在move后,尽管新struct对象的地址有变动,但新的对象的content的地址也没有变化,新的对象自引用指针也没有变动。因此,对象的自引对应的指针正确指向的content所对应的地址,之前的隐患消除。
2、相关问题
不知大家注意到没有,上面的代码,SelfRef对象被安排了2次move,但是经过2次move的对象,其自引用指针的内存还是指次第1次move前的对象的地址位置。如果多次move后,这些变量被drop后,原始的地址没清空或初始化,那么解引用就会出现"指引悬垂"的问题。
但是,在这些变量的地址没有被清空或初始化前,而且这个地址看似"还能"正确解引用出"正确"的值。一方面,造成了假象;另一方面,也把问题隐藏了起来,在很多情况下,看起来让程序在大部分的情况下运行结果表现正确。但其实已经造成潜在的安全隐患。
正因为此,异步的Future才需要通过Pin来堵住这个安全的漏洞。这个才是Pin的价值所在。