天天看点

腐蚀rust哪个键可以拆除_用Rust写脚本语言(十六):Rust代码就是一层套一层套一层套一层的大洋葱...

Cowsay:用Rust写脚本语言(零):目录​zhuanlan.zhihu.com

腐蚀rust哪个键可以拆除_用Rust写脚本语言(十六):Rust代码就是一层套一层套一层套一层的大洋葱...

对不起各位,想法中预告的「大一统」至少还要再等一篇文章了。

我最大的错误就是,在预估项目进度的时候,总是习惯性的把Rust当成Python去考虑任务量。

实际上,Rust还是更像C++一点。那些让项目无比扎实的boilerplate,无法省略也不应该省略。这是一件麻烦事,好的那种。当然了,Rust提供了比C++强大得多的宏系统来尽量精简重复代码,只是我不太喜欢用而已。

更严重的问题是,Rust中的boilerplate还有可观的一部分是由于生命周期的原因才引入的。这些代码我在写C++的时候也没遇到过,所以真正遇到之前根本无法想到,居然会有这么这么这么大的一坨……

今天不放项目地址了,现在的项目里几乎所有的模块都被暂时注释掉了,实在没眼看。新添加的

SharedMemory

的用法在后面的文章里再详细讲,这篇文章的重点是吐槽

impl<'a, O> RemoteObjectGuard<'a, O> {
    pub fn read(&self) -> ReadRemoteObject<O> {
        let read = ReadRemoteObject {
            guard: self
                .guard
                .objects
                .get(&self.object_id)
                .expect("segfault")
                .object
                .read()
                .unwrap(),
        };
        read
    }

    pub fn write(&self) -> WriteRemoteObject<O> {
        let write = WriteRemoteObject {
            guard: self
                .guard
                .objects
                .get(&self.object_id)
                .expect("segfault")
                .object
                .write()
                .unwrap(),
        };
        write
    }
}
           

恍惚间我真的以为自己在写JavaScript。

首先我需要一个散列表,表本身可能被多个用户插入/删除元素,所以需要

Arc<RwLock<...>>

。表本身放进锁还不够,键生成器也要放进去,于是我们有

pub struct SharedMemory<O> {
    internal: Arc<RwLock<SharedMemoryPriv<O>>>,
}
struct SharedMemoryPriv<O> {
    objects: HashMap<usize, CountedObject<O>>,
    object_id: Inc,
}
           

(哦对了,这个

XXPriv

还埋了一个坑,谁会知道

priv

居然也是Rust的保留字……)

那么

CountedObject

又是什么东西?在不改变散列表本身的情况下,写一个对象不应该阻塞对另一个对象的读写,所以每一个对象都应该存入一个单独的锁中

struct CountedObject<O> {
    object: RwLock<O>,
    count: usize,
}
           

当有人想要访问对象时,

count

就会增加1;用户不再需要它了,

count

就会减去1。通过类似于引用计数的方式决定一个对象是否需要存留在散列表中。说是「类似于」是因为对象之间不会互相引用,所有的引用计数来自于外部,因此没有循环引用的问题。

也许你猜到了,「用户不再需要它了」是通过RAII语义,实现

Drop

来表达的。多年以后,我成为了自己最讨厌的人……

也就是说,对一个对象的释放持有操作有可能会影响散列表(没人引用了的对象要从表中删除),所以交给用户的对象代理不能仅仅包含对对象本身的控制权,还要能操作整个表

pub struct RemoteObject<O> {
    internal: Arc<RwLock<SharedMemoryPriv<O>>>,
    object_id: usize,
}
           

既然这里出现了

RwLock<...>

, 有了之前文章中的惨痛教训,我自然会意识到,

RwLockReadGuard<...>

是不可能省略的

pub struct RemoteObjectGuard<'a, O> {
    guard: RwLockReadGuard<'a, SharedMemoryPriv<O>>,
    object_id: usize,
}
           

然后呢?对于整个表的

RwLockReadGuard

都出现了,对于单个对象的还会远吗?

pub struct ReadRemoteObject<'a, O> {
    guard: RwLockReadGuard<'a, O>,
}
           

和表不同的是,单个对象还可以被可变借用

pub struct WriteRemoteObject<'a, O> {
    guard: RwLockWriteGuard<'a, O>,
}
           

七个,整整七个结构体,配上方法整整要写170行,其中就包括最上面的那两个方法,用于从整个表的

RwLockReadGuard

中提取对一个对象的guard。

这事告诉我们,Rust和Lock和RAII是真的真的不搭调。如果有机会,我要在Shattuck中试试看,有没有不这么反人类的写法。

哎,真是一门「素雅」的语言啊……

Cowsay:用Rust写脚本语言(零):目录​zhuanlan.zhihu.com

腐蚀rust哪个键可以拆除_用Rust写脚本语言(十六):Rust代码就是一层套一层套一层套一层的大洋葱...