You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Rust中Pin与非Pin函数对比:如何体现Pin的作用?

如何让Rust的Pin特性体现实际作用

你的代码看不到Pin效果的核心原因是:你使用的i32、&str都是实现了Unpin trait的类型。对于这类类型,Pin<T>不会施加任何移动限制——即使被Pin包裹,它们仍然可以像普通值一样被移动,所以带Pin和不带Pin的函数表现完全一致。

要看到Pin的实际作用,你需要使用非Unpin类型,也就是那些不能被安全移动的类型,最典型的就是自引用结构体(结构体内部持有指向自身的指针)。下面是修改后的代码示例:

use std::fmt;
use std::pin::Pin;
use std::ptr;
use std::marker::PhantomPinned;

// 定义一个自引用结构体,用PhantomPinned标记为!Unpin(不可安全移动)
#[derive(Debug)]
struct SelfRef {
    data: String,
    ptr_to_data: *const String,
    _pin: PhantomPinned,
}

impl SelfRef {
    // 创建自引用结构体的安全方法:必须在Pin<Box<Self>>中初始化
    fn new(s: String) -> Pin<Box<SelfRef>> {
        let mut boxed = Box::new(SelfRef {
            data: s,
            ptr_to_data: ptr::null(),
            _pin: PhantomPinned,
        });
        // 获取data的指针
        let ptr = &boxed.data as *const String;
        // 因为结构体被Pin在Box里,所以可以安全地设置自引用
        unsafe {
            Pin::as_mut(&mut boxed).get_unchecked_mut().ptr_to_data = ptr;
        }
        Pin::new(boxed)
    }

    // 尝试读取自引用指向的数据
    fn read_data(self: Pin<&SelfRef>) -> &str {
        unsafe { &*self.ptr_to_data }
    }
}

// 不带Pin的情况:移动自引用结构体后,自引用会失效
fn without_pin(mut s: Box<SelfRef>) {
    let original_ptr = s.ptr_to_data;
    // 移动结构体(这里只是重新赋值,模拟移动)
    let moved = s;
    // 尝试读取移动后的自引用,会导致未定义行为(UB)
    unsafe {
        println!("Without Pin, moved data: {}", &*moved.ptr_to_data);
        println!("Original ptr now points to: {}", &*original_ptr); // 这里会访问已失效的内存
    }
}

fn with_pin(pin: Pin<Box<SelfRef>>) {
    // Pin阻止了我们直接移动内部的结构体
    println!("With Pin, data: {}", pin.read_data());
    // 无法像普通Box一样取出内部结构体,避免了移动导致的自引用失效
    // let moved = pin.into_inner(); // 编译错误,因为SelfRef是!Unpin
}

fn main() {
    // 带Pin的情况:安全使用自引用
    let pin = SelfRef::new("Hello Pin!".to_string());
    with_pin(pin);

    // 不带Pin的情况:移动后自引用失效(注意:实际运行可能崩溃或输出乱码,属于UB)
    let mut unsafe_box = Box::new(SelfRef {
        data: "Unsafe Move".to_string(),
        ptr_to_data: ptr::null(),
        _pin: PhantomPinned,
    });
    let ptr = &unsafe_box.data as *const String;
    unsafe_box.ptr_to_data = ptr;
    without_pin(unsafe_box);
}

关键说明:

  • PhantomPinned:这个标记trait会让结构体自动实现!Unpin,告诉Rust该类型不能被安全移动。
  • 自引用初始化:必须在Pin<Box<Self>>中初始化自引用,因为Pin保证了结构体的内存位置不会改变,所以自引用的指针永远有效。
  • Pin的限制:对于!Unpin类型,Pin<Box<T>>无法通过into_inner()取出内部的Box<T>,也就阻止了移动操作,避免了自引用失效导致的悬垂指针。
  • 不带Pin的危险:如果移动自引用结构体,内部的指针会指向原来的内存位置,而原来的内存已经被释放或重新分配,访问它会导致未定义行为(程序崩溃、输出乱码等)。

通过这个示例,你就能清晰看到Pin的作用:固定住非Unpin类型的内存位置,确保自引用指针始终有效,避免移动导致的内存安全问题。

内容的提问来源于stack exchange,提问作者mascai

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 02:43:14