6

Rust: Pin与刻舟求剑

 2 years ago
source link: https://zhuanlan.zhihu.com/p/385965048
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Rust: Pin与刻舟求剑

CrackingOysters,源于编程珠玑,也是公众号

如果你还在挣扎着理解Rust的Pin,那么可以来看看刻舟求剑与Pin的关系。

Rust的Pin概念,可能比较难理解,就算是一些编程老手也会研究一阵子。因为其他语言(我接触的)都没有提及这个概念。

Pin中文意思为固定,Rust中的Pin也是取固定的意思——将value固定住,避免被移动。

所以为了理解Pin,我们需要明白以下两个问题

  1. 为什么需要固定value,不让移动?
  2. Pin是如何禁止移动的?

第一个问题,为什么需要固定value,不让移动呢?

这是因为有些value含有自引用。自引用,即存储了一个指向自身的指针,如下图,

v2-a0489092c3b6028bf2fc41df070f193f_720w.jpg
struct Book {
  name: String,
  my_name: &String,
}

结构体Book包含了指向自己name的引用/指针。这个指针实际上是一串长这样的数字0x800x8fff。

这样的结构Book,在move以后,指针的数值没有变化,导致仍然指向过去的地方。那么这个指针已经是无效了,结构体book也因此废掉了,不能再被使用。

古代有个典故很切合这种场景,那就是刻舟求剑——

楚国有个渡江的人,他的剑从船中掉到水里。他立刻在那船(舷)上刻了记号,说:“这是我的剑掉下去的地方。”船到目的地后停了下来,这个楚国人从他刻记号的地方跳到水里寻找剑。船已经行驶了,但是剑没有移动,像这样寻找剑,不是很糊涂吗?

指针就像掉下去的剑,一直指着一个地方,当你移动船(Book)以后,你的剑还在原来的地方,是不会找到剑的。

所以当剑掉进湖里,刻完记号以后(有自引用的指针),就不能移动船了!而这时候我们的帮手Pin就登场了~

关于move的更多内容,请看什么是move?理解C++ Value categories,move, move in Rust

有些读者,可能会觉得这个自引用结构有啥用处啊,为了这个东西,搞得这么复杂,有必要吗。

这主要是为了支持Rust的异步编程。如果想了解异步编程的概念可以阅读Coroutine, 异步,同步,async, await。Rust 的异步编程有其特点。下面略微阐述一下。

Rust异步编程(协程,coroutine)

三言两语是讲不明白Rust的异步编程的,毕竟内容很多。

Rust的异步编程采取的是Poll模型,与C++/JavaScript/Python的方式不一样。

C++/JavaScript在运行future/promise/async task的时候会一直运行,直到被挂起。(如果对C++的协程感兴趣,可以看The Coroutine in C++ 20 协程初探

而Rust的future会先挂起,只有当被executor poll的时候才会运行,有点延迟运行的意味。

让我们通过代码来看看这区别意味着什么,比如下面的伪代码,

function f()
{
   print “enter";
   await Promise();
   print "leave";
}
print "start"
f()
print "end"

如果代码是JavaScript,会输出 start->enter->end->leave (假设promise会花点时间)。

如果代码是Rust,则会输出start->end->enter->leave。

对比输出,我们会发现Rust会先输出start,end,这是因为f是异步task(future),在第一次被调用的时候不会被运行,只有当被Executor poll的时候才会运行。

而JavaScript会先输出start, enter,这是因为JavaScript在调用Promise的时候会直接进入函数f的内部执行,直到被挂起(遇到await)。

(rust的异步未完待续)

下面,先让我们看看第二个问题——Pin是如何禁止移动呢?

Pin之技

Pin是如何实现禁止移动呢?其实很简单,把你想要固定的type用Pin 包装(wrap)一下。这样子,操作Pin<T>以后,你就不能通过Pin<T>获取&mut T,也就无法move T。

为什么这时候无法move T?因为如果你要move一个东西,必须拥有所有权,或者有它的可变引用&mut T。

当有可变引用可以通过replace/swap来move T。 现在你无法获取&mut T当然也就无法move了。而你既然用Pin包装了一下,也就说明你不再拥有T了。

如果硬是要类比一下的话,就相当于你把钱包给了你老婆(老婆大人是Pin)。这样子,你就不能移动你的钱包了,你要通过老婆作为中介去拿钱,只能拿一部分,而不能把整个钱包移动起来了。 (所以钱包不能上交那)

对Rust感兴趣可以看总结Rust那些难理解的点(大量更新于6月16日)

将上述伪代码写成真正的代码,

JavaScript版本

async function f() {
    console.log("enter");
    await new Promise((resolve, reject)=> setTimeout(()=> { console.log("promise resolved"); resolve(); }, 2000));
    console.log("leave");
}
function main() {
    console.log("start");
    f();
    console.log("end");
}
main()

Rust版本

use tokio::time;
use mini_redis::{client, Result};
async fn promise() {
    println!("promise resolved.");
}
async fn f() {
    println!("enter");
    promise().await;
    println!("leave");
}

#[tokio::main]
pub async fn main() -> Result<()> {
    println!("start");
    let task:tokio::task::JoinHandle<_>= tokio::spawn(async {f().await;});
    println!("end");
    time::sleep(time::Duration::from_secs(10)).await;

    Ok(())
}

刻舟求剑的故事 刻舟求剑的故事的道理

rust-lang/rfcs

Rust的数据移动到底有没有移动数据在内存里的位置?


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK