rust高级 异步编程 二 pin(2)

简介: rust高级 异步编程 二 pin

rust高级 异步编程 二 pin(1)https://developer.aliyun.com/article/1392111


原因是 test2.b 指针依然指向了旧的地址,而该地址对应的值现在在 test1 里,最终会打印出意料之外的值。

如果大家还是将信将疑,那再看看下面的代码:

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);// 在两个可变位置交换值
    test1.a = "I've totally changed now!".to_string();
    println!("a: {}, b: {}", test2.a(), test2.b());
}

下面的图片也可以帮助更好的理解这个过程:

Pin 在实践中的运用

将值固定到栈上

回到之前的例子,我们可以用 Pin 来解决指针指向的数据被移动的问题:

use std::pin::Pin;
use std::marker::PhantomPinned;
#[derive(Debug)]
struct Test {
    a: String,
    b: *const String,
    _marker: PhantomPinned,
}
impl Test {
    fn new(txt: &str) -> Self {
        Test {
            a: String::from(txt),
            b: std::ptr::null(),
            _marker: PhantomPinned, // 这个标记可以让我们的类型自动实现特征`!Unpin`
        }
    }
    fn init(self: Pin<&mut Self>) {
        let self_ptr: *const String = &self.a;
        let this = unsafe { self.get_unchecked_mut() };
        this.b = self_ptr;
    }
    fn a(self: Pin<&Self>) -> &str {
        &self.get_ref().a
    }
    fn b(self: Pin<&Self>) -> &String {
        assert!(!self.b.is_null(), "Test::b called without Test::init being called first");
        unsafe { &*(self.b) }
    }
}

我们使用了一个标记类型 PhantomPinned 将自定义结构体 Test 变成了 !Unpin,因此该结构体无法再被移动。

一旦类型实现了 !Unpin ,那将它的值固定到栈( stack )上就是不安全的行为,因此在代码中我们使用了 unsafe 语句块来进行处理,你也可以使用 pin_utils 来避免 unsafe 的使用。

Rust 中的 unsafe 其实没有那么可怕,虽然听上去很不安全,但是实际上 Rust 依然提供了很多机制来帮我们提升了安全性,因此不必像对待 Go 语言的 unsafe 那样去畏惧于使用 Rust 中的 unsafe ,大致使用原则总结如下:没必要用时,就不要用,当有必要用时,就大胆用,但是尽量控制好边界,让 unsafe 的范围尽可能小

此时,再去尝试移动被固定的值,就会导致编译错误:

pub fn main() {
    // 此时的`test1`可以被安全的移动
    let mut test1 = Test::new("test1");
    // 新的`test1`由于使用了`Pin`,因此无法再被移动,这里的声明会将之前的`test1`遮蔽
    let mut test1 = unsafe { Pin::new_unchecked(&mut test1) };
    Test::init(test1.as_mut());
    let mut test2 = Test::new("test2");
    let mut test2 = unsafe { Pin::new_unchecked(&mut test2) };
    Test::init(test2.as_mut());
    println!("a: {}, b: {}", Test::a(test1.as_ref()), Test::b(test1.as_ref()));
    std::mem::swap(test1.get_mut(), test2.get_mut());
    println!("a: {}, b: {}", Test::a(test2.as_ref()), Test::b(test2.as_ref()));
}

是的,Rust 并不是在运行时做这件事,而是在编译期就完成了,因此没有额外的性能开销!来看看报错:

error[E0277]: `PhantomPinned` cannot be unpinned
   --> src/main.rs:47:43
    |
47  |     std::mem::swap(test1.get_mut(), test2.get_mut());
    |                                           ^^^^^^^ within `Test`, the trait `Unpin` is not implemented for `PhantomPinned`

需要注意的是固定在栈上非常依赖于你写出的 unsafe 代码的正确性。我们知道 &'a mut T 可以固定的生命周期是 'a ,但是我们却不知道当生命周期 'a 结束后,该指针指向的数据是否会被移走。如果你的 unsafe 代码里这么实现了,那么就会违背 Pin 应该具有的作用!

一个常见的错误就是忘记去遮蔽( shadow )初始的变量,因为你可以 drop 掉 Pin ,然后在 &'a mut T 结束后去移动数据:

fn main() {
   let mut test1 = Test::new("test1");
   let mut test1_pin = unsafe { Pin::new_unchecked(&mut test1) };
   Test::init(test1_pin.as_mut());
   drop(test1_pin);
   println!(r#"test1.b points to "test1": {:?}..."#, test1.b);
   let mut test2 = Test::new("test2");
   mem::swap(&mut test1, &mut test2);
   println!("... and now it points nowhere: {:?}", test1.b);
}
固定到堆上

将一个 !Unpin 类型的值固定到堆上,会给予该值一个稳定的内存地址,它指向的堆中的值在 Pin 后是无法被移动的。而且与固定在栈上不同,我们知道堆上的值在整个生命周期内都会被稳稳地固定住。

use std::pin::Pin;
use std::marker::PhantomPinned;
#[derive(Debug)]
struct Test {
    a: String,
    b: *const String,
    _marker: PhantomPinned,
}
impl Test {
    fn new(txt: &str) -> Pin<Box<Self>> {
        let t = Test {
            a: String::from(txt),
            b: std::ptr::null(),
            _marker: PhantomPinned,
        };
        let mut boxed = Box::pin(t);
        let self_ptr: *const String = &boxed.as_ref().a;
        unsafe { boxed.as_mut().get_unchecked_mut().b = self_ptr };
        boxed
    }
    fn a(self: Pin<&Self>) -> &str {
        &self.get_ref().a
    }
    fn b(self: Pin<&Self>) -> &String {
        unsafe { &*(self.b) }
    }
}
pub fn main() {
    let test1 = Test::new("test1");
    let test2 = Test::new("test2");
    println!("a: {}, b: {}",test1.as_ref().a(), test1.as_ref().b());
    println!("a: {}, b: {}",test2.as_ref().a(), test2.as_ref().b());
}
将固定住的 Future 变为 Unpin

async 函数返回的 Future 默认就是 !Unpin 的。

但是,在实际应用中,一些函数会要求它们处理的 Future 是 Unpin 的,此时,若你使用的 Future 是 !Unpin 的,必须要使用以下的方法先将 Future 进行固定:

  • Box::pin, 创建一个 Pin>
  • pin_utils::pin_mut!, 创建一个 Pin<&mut T>
    固定后获得的 Pin 和 Pin<&mut T> 既可以用于 Future ,又会自动实现 Unpin。
use pin_utils::pin_mut; // `pin_utils` 可以在crates.io中找到
// 函数的参数是一个`Future`,但是要求该`Future`实现`Unpin`
fn execute_unpin_future(x: impl Future<Output = ()> + Unpin) { /* ... */ }
let fut = async { /* ... */ };
// 下面代码报错: 默认情况下,`fut` 实现的是`!Unpin`,并没有实现`Unpin`
// execute_unpin_future(fut);
// 使用`Box`进行固定
let fut = async { /* ... */ };
let fut = Box::pin(fut);
execute_unpin_future(fut); // OK
// 使用`pin_mut!`进行固定
let fut = async { /* ... */ };
pin_mut!(fut);
execute_unpin_future(fut); // OK
总结

脑袋里已经快被 Pin 、 Unpin 、 !Unpin 整爆炸了,没事,我们再来火上浇油下:

  • 若 T: Unpin ( Rust 类型的默认实现),那么 Pin<'a, T> 跟 &'a mut T 完全相同,也就是 Pin 将没有任何效果, 该移动还是照常移动
  • 绝大多数标准库类型都实现了 Unpin ,事实上,对于 Rust 中你能遇到的绝大多数类型,该结论依然成立 ,其中一个例外就是:async/await 生成的 Future 没有实现 Unpin
  • 你可以通过以下方法为自己的类型添加 !Unpin 约束:
  • 使用文中提到的 std::marker::PhantomPinned
  • 使用nightly 版本下的 feature flag
  • 可以将值固定到栈上,也可以固定到堆上
  • 将 !Unpin 值固定到栈上需要使用 unsafe
  • 将 !Unpin 值固定到堆上无需 unsafe ,可以通过 Box::pin 来简单的实现
  • 当固定类型 T: !Unpin 时,你需要保证数据从被固定到被 drop 这段时期内,其内存不会变得非法或者被重用
相关文章
|
6天前
|
数据采集 存储 Rust
Rust高级爬虫:如何利用Rust抓取精美图片
Rust高级爬虫:如何利用Rust抓取精美图片
|
6天前
|
Rust Java 调度
Rust中的异步编程利器:Tokio框架解析
在Rust生态系统中,Tokio已经成为异步编程的首选框架。本文将对Tokio进行深入探讨,分析其关键特性、工作原理以及如何在Rust项目中使用Tokio进行高效的异步编程。我们将通过示例代码展示Tokio如何简化异步操作,提升程序性能,并讨论Tokio在处理并发任务时的优势。
|
6天前
|
Rust 安全 程序员
Rust中的异步编程:Futures与Async/Await的深入解析
Rust作为一种系统级编程语言,近年来因其内存安全、高性能和并发处理能力而受到广泛关注。在Rust中,异步编程是实现高效并发处理的关键。本文将探讨Rust中的异步编程概念,详细介绍Futures与Async/Await这两种主要实现方式,并解析它们在实际应用中的优势与挑战。
|
6天前
|
Rust 安全
rust高级 异步编程 二 pin(1)
rust高级 异步编程 二 pin
67 0
|
6天前
|
Rust 调度
rust高级 异步编程 一 future(2)
rust高级 异步编程 一 future
40 0
|
6天前
|
存储 Rust 并行计算
rust高级 异步编程 一 future(1)
rust高级 异步编程 一 future
60 0
|
6天前
|
Rust 开发者
rust 笔记 高级错误处理(二)
rust 笔记 高级错误处理
61 0
|
6天前
|
Rust
rust 笔记 高级错误处理(一)
rust 笔记 高级错误处理
57 0
|
6天前
|
Rust 安全 程序员
|
6天前
|
Rust 安全 程序员
Rust vs Go:解析两者的独特特性和适用场景
在讨论 Rust 与 Go 两种编程语言哪种更优秀时,我们将探讨它们在性能、简易性、安全性、功能、规模和并发处理等方面的比较。同时,我们看看它们有什么共同点和根本的差异。现在就来看看这个友好而公平的对比。