Cowsay:用Rust写脚本语言(零):目录zhuanlan.zhihu.com
对不起各位,想法中预告的「大一统」至少还要再等一篇文章了。
我最大的错误就是,在预估项目进度的时候,总是习惯性的把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