0%

在思考一个生命周期问题的时候搞了个微型玩具出来。

假设我们想在 Rust 里实现一个类似下面这样的类型(省略掉所有生命周期参数):

1
2
3
4
struct Guardian { /* ... */ }
impl Guardian {
fn guard<T: ?Sized>(&self, _: &T) {}
}

我们希望这东西能起到如下作用:传给 guard 的引用在 Guardian 实例死亡之前不能失效。例如我们可以有如下 demo 代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
fn main() {
let a = Box::new(1);

let guardian = Guardian::new();

let b = Box::new(2);
let c = Box::new(3);

guardian.guard(&a);
guardian.guard(&b);
guardian.guard(&c);

// Uncomment this line to get a compile error!
// drop(c);

drop(guardian);
}

上面这段代码要能正确编译,并且如果我们提前把一个曾经传给过 guard 的对象给移走,那么就要报错。

Step 1

首先的想法当然是要求传入 guard 的引用至少具有 &self 的声明周期,于是我们可以写出如下代码:

1
2
3
4
struct Guardian {}
impl Guardian {
fn guard<'a, T: ?Sized>(&'a self, _: &'a T) {}
}

这显然是不对的。生命周期参数 'a 会被自动推断为 &self&T 的生命周期中更小的那个,导致它根本不能防止 &T 引用的对象被提前移走。

Step 2

如果 guard 方法上 &self 的生命周期参数会变小,那我们固定住它就好了。为此我们得给 Guardian 加个生命周期。得到的代码如下:

1
2
3
4
struct Guardian<'a>(PhantomData<&'a ()>);
impl<'a> Guardian<'a> {
fn guard<'b, T: ?Sized>(&'a self, _: &'b T) where 'b: 'a {}
}

遗憾的是,这也是不对的。问题出在 subtyping 上面:&'a Guardian<'a>'aGuardian<'a> 都是协变的,然后 Guardian<'a>'a 也是协变的;于是编译器仍然可以搞一个更小的生命周期 '0 出来,然后把 &'a Guardian<'a> 转成 &'0 Guardian<'0>,这样就寄了。

Step 3

我们希望 &self 不能对 Self 协变,所以我们可以搞成不变。一种手段是用 &mut self 代替 &self

1
2
3
4
struct Guardian<'a>(PhantomData<&'a ()>);
impl<'a> Guardian<'a> {
fn guard<'b, T: ?Sized>(&'a mut self, _: &'b T) where 'b: 'a {}
}

这样做又是不对的,它会爆炸。参考上面的 demo,虽然编译器能正确推断 'a,但是 'a 至少是从 c 出生到 guardian 死亡这么长,而这段里面叠了 3 个对 guardian 的可变引用,显然过不了编译。为此我们得换另一种手段,让 Self 变成不变的,这可以通过把引用包进 UnsafeCell 来实现(或者搞个 PhantomData<&mut ()> 也行):

1
2
3
4
struct Guardian<'a>(UnsafeCell<&'a PhantomData<()>>);
impl<'a> Guardian<'a> {
fn guard<'b, T: ?Sized>(&'a mut self, _: &'b T) where 'b: 'a {}
}

然而,这又又是不对的。如果这么实现的话,demo 里面虽然 drop(c) 会报错,但 drop(guardian) 那行同样会报。注释掉 drop(c) 并不解决这个问题,但是如果转而把 drop(guardian) 注释掉,那么反而没错了。问题出在哪呢?编译器会给出类似下面这样的报错:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
error[E0505]: cannot move out of `guardian` because it is borrowed
--> src/main.rs:32:10
|
21 | let guardian = Guardian::new();
| -------- binding `guardian` declared here
...
26 | guardian.guard(&a);
| ------------------ borrow of `guardian` occurs here
...
32 | drop(guardian);
| ^^^^^^^^
| |
| move out of `guardian` occurs here
| borrow later used here

Fantastic!显然,编译器推断出的 'a 是要超出 drop(guardian) 的,由于我们在 guard 的时候给 &self 上了 'a 的生命周期约束,所以编译器觉得 drop 之后仍然存在对 guardian 的引用。

Final

问题已经很清楚了,guard 方法不能给 &self 施加 'a 生命周期约束,所以我们要换一个更小的生命周期。

1
2
3
4
5
6
7
8
9
struct Guardian<'a>(UnsafeCell<&'a PhantomData<()>>);
impl<'a> Guardian<'a> {
fn guard<'b, 'c, T: ?Sized>(&'b self, _: &'c T)
where
'c: 'a,
'a: 'b,
{
}
}

就是它了。现在它完美地完成了我们的预设目标,成为了一个引用的守护者。

从 Hexo v4.3.0 迁移到了 Hexo 5,顺带着包括 Node.js 版本和 NexT 主题都升级了。

升级完成后遇到一个奇怪的问题:本地 npx hexo g 的时候一切正常,npx hexo d 部署到远端就出现问题。

最后通过对比本地和远端部署的 JavaScript 和 CSS 发现了问题。本地的这些文件——包括 npx hexo g 生成的文件——都没有最小化,而远端的文件却最小化了。那么问题很明显出在 Cloudflare 身上:我打开了它的 Auto Minify 功能用来减小文件体积,这个功能在我更新前没有问题,但在我更新后,却不知为何会把一些 JavaScript 逻辑和 CSS 样式“优化”掉。

解决方案也很简单,关掉 Auto Minify,然后清除 Cloudflare 缓存使其立即生效即可。

将近两个月前搬到了独立卫浴的新宿舍,除了电费之外也要开始交水费了。

  • 水费是只对热水收钱,每立方米热水 32 元人民币。考虑到热水实际上非常热,水龙头往出放的实际上都是很少的热水与大量的冷水混合的温水,所以真正用的热水是要便宜个几倍的。饶是如此,每天也要花掉 3 块 2 的水费。这数字正巧是 32 元的十分之一,所以宿舍每天消耗一百升热水,想想还是挺恐怖的。作为参考,北京的自来水费是三个阶梯,每立方米分别是 5、7、9 元人民币。不知道热水里有没有摊入一定比例的本应支付的冷水水费,如果不考虑的话,那么看来烧热水是挺花钱的。
  • 电费是每度电 0.5103 元,这个数有零有整的,大体上就是一块钱能充两度电差一点点。这个数字就正好是北京电费里“执行居民价格的非居民用户”这一项计费模式的费用,好像和学生公寓挺搭的;学校也不在这上征收税赋,好像还有点良心。宿舍每天大概会消耗两三四度电,相比水费而言,电费的数额显得不那么稳定,不过也大差不差。宿舍的冰箱的节能标识写明了每天耗费半度电,所以整个宿舍大概可以抽象成六台电冰箱。

水电费一旦用完就会停热水停电,即使立即充值,到生效也有一个延迟,更何况每天 23:00 ~ 24:30 还不能充电费,这要是大夏天的晚上空调突然没了,硬挨一个半小时可以说是人间地狱。某天我突然心血来潮查了查电费发现还剩两度半,那时的恐慌属于是记忆犹新。

结论是必须得有一个机制,在水电费快用完的时候提醒充值。

阅读全文 »

这是一篇不太水的水文。

《分布式系统导论》课程做的 MIT 6.824 的最后一个实验 Lab 4B 终于做完了,过了除了 Challenge 1 以外的所有测试。这个实验挺坑的,虽然 Lab 2 的 Raft 也很难,但是毕竟是复现论文;Lab 4B 核心的东西必须完全自己设计。我在某一个晚上突然开窍,悟出了一个直观简洁的做法,先不管正确性,至少能够过掉测试;特地掏出大半年没更的博客记录下来。

阅读全文 »

这是一篇论文阅读笔记。

Leap 是一个内核态的、面向分布式 NVM 的缓存预取和逐出策略,可以运行在大部分基于 NVM 的存储系统之下;在 Linux 内核中实现的 Leap 使访问远程页的中位数延迟和尾延迟分别降低 104.4× 和 22.62×,带来最高 10.16× 的性能提升。

阅读全文 »