您的当前位置:首页正文

Rust: 关于Pin以及move前后分析

2024-11-22 来源:个人技术集锦

一、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代码如下:


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,
        }
    }
   // stack
    fn set_ref_value_by_ref_mut(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;
    }
    // heap 
    fn set_ref_value_by_box_pin(mut pinned_obj: Pin<Box<Self>>) ->Pin<Box<Self>>{
        let content_ptr = &pinned_obj.content as *const String;
        unsafe { 
            pinned_obj.as_mut().get_unchecked_mut()._ptr = content_ptr
        };
        pinned_obj
        
    }
}
fn detect_free_and_self_ref(){
    println!("\n----Free、SelfRef两个unpin对象move前后分析------------");
    let free = Free::default();
    let mut self_ref = SelfRef::default();
    self_ref.set_ref_value();
    println!("\n-----------before move-------------");
    println!("free                     content:{:?}  content  内存指针:{:p}",free.content,&free.content);
    // Free after move
    println!("-----------after move-------------");
    let free_move = free; //第1次move操作
    println!("free_move                content:{:?}  content   内存指针:{:p}",free_move.content,&free_move.content);
    // SelfRef before move 
    println!("\n-----------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_move = self_ref; //第1次move操作
    println!("self_ref_move            内存地址:{:p}  content    内存地址:{:p}",&self_ref_move,&self_ref_move.content);
    println!("self_ref_move._ptr      对应value:{:?}  自引用指针的内存地址:{:?}",unsafe{&*self_ref_move._ptr},self_ref_move._ptr);

    // SelfRef  move again
    println!("-----------after move again -------------");
    let self_ref_move2 = self_ref_move; //第2次 move操作
    println!("self_ref_move2            内存地址:{:p}  content    内存地址:{:p}",&self_ref_move2,&self_ref_move2.content);
    println!("self_ref_move2._ptr      对应value:{:?}  自引用指针的内存地址:{:?}",unsafe{&*self_ref_move2._ptr},self_ref_move2._ptr);
}

fn detect_self_ref_pin_by_ref_mut(){
    let mut obj = SelfRefPinned::default();
    // obj不执行shadow
    let pinned_obj = unsafe { Pin::new_unchecked( &mut obj) };
    // obj => 执行shadow 
    // let obj =unsafe { Pin::new_unchecked( &mut obj) }; //
    // 经过shadow后,obj才算Pin住!
    SelfRefPinned::set_ref_value_by_ref_mut(pinned_obj);

    println!("\n----Pin在stack上: !unpin trait结构体对象move前后分析------------");
    
    println!("\n-----------before move-------------");
    println!("obj             内存地址:{:p}  content    内存地址:{:p}",&obj,&obj.content);
    println!("obj._ptr       对应value:{:?}  自引用指针的内存地址:{:?}",unsafe{&*obj._ptr},obj._ptr);
    println!("-----------after move-------------");
    
    // self_ref_pin 结构体 move
    let obj_move = obj;
    println!("obj_move        内存地址:{:p}  content    内存地址:{:p}",&obj_move,&obj_move.content);
    println!("obj_move._ptr  对应value:{:?}  自引用指针的内存地址:{:?}",unsafe{&*obj_move._ptr},obj_move._ptr);
    println!("\n");

}

fn detect_sef_ref_pin_by_pin_box(){
    let obj = SelfRefPinned::default();// obj已经被move掉了,已经不再能move了
    let pinned_obj = Box::pin(obj);
    let pinned_obj = SelfRefPinned::set_ref_value_by_box_pin(pinned_obj);

    println!("\n----Pin在堆上: Pin<Box<T> where T:!unpin对象move前后分析------------");
    // Pin<&mut SelfRefPinned> before move
    println!("\n-----------before move-------------");
    println!("pinned_obj            内存地址:{:p}  content    内存地址:{:p}",&pinned_obj,&pinned_obj.content);
    println!("pinned_obj._ptr      对应value:{:?}  自引用指针的内存地址:{:?}",unsafe{&*pinned_obj._ptr},pinned_obj._ptr);

    //Pin<&mut SelfRefPinned> after move
    println!("-----------after move-------------");
    let pinned_obj_move = pinned_obj; //move 操作
    println!("pinned_obj_move      内存地址:{:p}  content    内存地址:{:p}",&pinned_obj_move,&pinned_obj_move.content);
    println!("pinned_obj_move._ptr 对应value:{:?}  自引用指针的内存地址:{:?}",unsafe{&*pinned_obj_move._ptr},pinned_obj_move._ptr);
    //drop(pinned_obj_move);
    println!("\n");
}

fn main() {
    detect_free_and_self_ref();
    detect_self_ref_pin_by_ref_mut();
    detect_sef_ref_pin_by_pin_box();
}


四、输出


----Free、SelfRef两个unpin对象move前后分析------------

-----------before move-------------
free                     content:""  content  内存指针:0x7ffd71b98f28
-----------after move-------------
free_move                content:""  content   内存指针:0x7ffd71b98f00

-----------before move-------------
self_ref                 内存地址:0x7ffd71b98ee0  content    内存地址:0x7ffd71b98ee0
self_ref._ptr           对应value:"hello world!"  自引用指针的内存地址:0x7ffd71b98ee0
-----------after move-------------
self_ref_move            内存地址:0x7ffd71b98ec0  content    内存地址:0x7ffd71b98ec0
self_ref_move._ptr      对应value:"hello world!"  自引用指针的内存地址:0x7ffd71b98ee0
-----------after move again -------------
self_ref_move2            内存地址:0x7ffd71b98ea0  content    内存地址:0x7ffd71b98ea0
self_ref_move2._ptr      对应value:"hello world!"  自引用指针的内存地址:0x7ffd71b98ee0

----Pin在stack上: !unpin trait结构体对象move前后分析------------

-----------before move-------------
obj             内存地址:0x7ffd71b98ec0  content    内存地址:0x7ffd71b98ec0
obj._ptr       对应value:"hello world!"  自引用指针的内存地址:0x7ffd71b98ec0
-----------after move-------------
obj_move        内存地址:0x7ffd71b98ea0  content    内存地址:0x7ffd71b98ea0
obj_move._ptr  对应value:"hello world!"  自引用指针的内存地址:0x7ffd71b98ec0


----Pin在堆上: Pin<Box<T> where T:!unpin对象move前后分析------------

-----------before move-------------
pinned_obj            内存地址:0x7ffd71b98f00  content    内存地址:0x55e54a4f5a10
pinned_obj._ptr      对应value:"hello world!"  自引用指针的内存地址:0x55e54a4f5a10
-----------after move-------------
pinned_obj_move      内存地址:0x7ffd71b98ee0  content    内存地址:0x55e54a4f5a10
pinned_obj_move._ptr 对应value:"hello world!"  自引用指针的内存地址:0x55e54a4f5a10

五、总结

1、几点观察

2、相关问题

正因为此,异步的Future才需要通过Pin来堵住这个安全的漏洞。这个才是Pin的价值所在。

3、Pin的效果

需要说明的是,如果对象是unpin的,再如何Pin也是pin不住的。

关于Pin的效果,不知道是否可以打个比喻来加深理解:

象一个学生上教育培训中课外班。如果碰到天资尚可(PhantomPinned)且愿意学(!unpin)的学生,课外班(Pin)是有效的;如果是天资愚笨的(没有PhantomPinned)或不愿意学(unpin)的,课外班是无效的,花钱起不到效果。

显示全文