问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

Rust中关于Pin的解读

创作时间:
作者:
@小白创作中心

Rust中关于Pin的解读

引用
1
来源
1.
https://juejin.cn/post/7401042923490623488

在Rust语言中,当遇到自引用结构需要进行移动操作时,可能会导致指针指向异常的问题。为了解决这一问题,Rust提供了Pin类型。本文将通过具体代码示例,深入探讨Pin的工作原理及其应用场景。

在Rust中,当存在自引用的结构时,在进行移动操作时,可能会导致指针指向原始地址不存在或者一直执行原地址,从而使得移动后指针指向的内容出现问题。为了解决这一问题,Rust提供了Pin类型来实现解决方案。Pin可以看作是对引用的再次封装。

下面是未使用Pin时,移动后导致指针指向异常的问题示例:

use std::pin::Pin;

fn main() {
    // pin就相当于额外包装了一层指针,移动的时候,直接整体移动,就不会出现问题
    // pin每次执行as_mut 会重新生成一个pin
    // pin只实现了Deref,没有实现Ref,此时只会返回一个原始引用,所以即使操作原始引用也不会影响当前的值
    // 当不使用Pin的时候,就会导致使用的是同一个引用,其他地方修改了引用当前就会受到影响
    let mut data1 = SelfReference::new("hello");
    // let mut data1 = unsafe { Pin::new_unchecked(&mut data1) };
    data1.init();
    let mut data2 = SelfReference::new("world");
    // let mut data2 = unsafe { Pin::new_unchecked(&mut data2) };
    data2.init();
    data1.print();
    data2.print();
    // 如果使用了Pin那么就相当于:重新定义了2个新的引用指向pin的数据,然后进行交换,此时新的引用和原来的引用是隔离的,所以不会相互影响
    // 但是如果不使用Pin那么就相当于:直接交换了2个引用,所以会相互影响
    std::mem::swap(&mut data1, &mut data2);
    data1.print();
    data2.print();
}

#[derive(Debug)]
struct SelfReference {
    name: String,
    // 在初始化后指向 name
    name_ptr: *const String,
}

impl SelfReference {
    pub fn new(name: impl Into<String>) -> Self {
        let name = name.into();
        SelfReference {
            name,
            name_ptr: std::ptr::null(),
        }
    }

    pub fn init(&mut self) {
        self.name_ptr = &self.name as *const String;
    }

    pub fn print(&self) {
        println!("{:?} {} {}", self, self.name, unsafe { &*self.name_ptr })
    }
}

下面是使用了Pin后,正常引用的示例:

use std::pin::Pin;

fn main() {
    let mut data1 = SelfReference::new("hello");
    let mut data1 = unsafe { Pin::new_unchecked(&mut data1) };
    data1.init();
    let mut data2 = SelfReference::new("world");
    let mut data2 = unsafe { Pin::new_unchecked(&mut data2) };
    data2.init();
    data1.print();
    data2.print();
    std::mem::swap(&mut data1, &mut data2);
    data1.print();
    data2.print();
}

#[derive(Debug)]
struct SelfReference {
    name: String,
    // 在初始化后指向 name
    name_ptr: *const String,
}

impl SelfReference {
    pub fn new(name: impl Into<String>) -> Self {
        let name = name.into();
        SelfReference {
            name,
            name_ptr: std::ptr::null(),
        }
    }

    pub fn init(&mut self) {
        self.name_ptr = &self.name as *const String;
    }

    pub fn print(&self) {
        println!("{:?} {} {}", self, self.name, unsafe { &*self.name_ptr })
    }
}

从上面的代码可以看出,使用Pin后,每次执行as_mut都会重新生成一个Pin,而Pin只实现了Deref,没有实现Ref,因此只会返回一个原始引用,即使操作原始引用也不会影响当前的值。如果不使用Pin,就会导致使用的是同一个引用,其他地方修改了引用当前就会受到影响。

再来看看Pin的实现细节:

从上图可以看出,Pin的实现主要涉及Derefget_refas_ref等方法。这些方法确保了Pin在移动时能够保持其内部数据的稳定性,避免了指针指向异常的问题。

本文介绍了Rust中Pin类型的基本使用场景和解决方案,通过对比使用Pin前后的代码示例,帮助读者理解Pin在处理自引用结构时的作用。希望本文能够帮助Rust开发者更好地理解和使用Pin类型。

本文原文来自掘金

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号