はじめに
Let's encryptのバグの原因はポインタに起因する実装ミスでした。
「Rustはいいぞ」と言うためだけにRustで実装した場合を検証してみます。
原因はなんだった?
詳しくは
https://jovi0608.hatenablog.com/entry/2020/03/09/094737
のステキなまとめを見たほうがいいのですが、
シンプルにすると、このような実装です。
fail.go
func main() {
var out []*int
for i := 0; i < 3; i++ {
out = append(out, &i)
}
fmt.Println("Values:", *out[0], *out[1], *out[2])
fmt.Println("Addresses:", out[0], out[1], out[2])
}
ValuesもAddressesも[0]~[2]で同じ値が表示されます。
ループカウンタを値渡しではなく、
参照渡しをして保管してしまったことが要因です。
同じような実装ミスをC++で書くとこんな感じです。
fail.cpp
#include <iostream>
#include <vector>
int main(){
std::vector<int*> out;
for(int i = 0; i < 3; i++){
out.push_back(&i);
}
std::cout << out[0] << std::endl;
std::cout << out[1] << std::endl;
std::cout << out[2] << std::endl;
std::cout << *out[0] << std::endl;
std::cout << *out[1] << std::endl;
std::cout << *out[2] << std::endl;
return 0;
}
これも同じような結果になります。
Rustで書いてみると
good.rust
fn main() {
let mut out:Vec<&i32> = vec![];
for i in 0..3{
out.push(&i);
}
println!("{:?}", out);
}
コンパイル時に、このようなエラーが出ます。
6 | out.push(&i);
| --- ^^ borrowed value does not live long enough
| |
| borrow later used here
Rustは生存期間を厳密に検証してくれるので、とても安全です!
実際のLet's encryptの実装ミスは、ループの中で関数呼び出しを挟んでいるので
気づくのが難しいとも感じました。
こんなケースではRustは本当に頼もしいです。
Rustはいいぞ
コンパイラにめっちゃ怒られるぞ。
コンパイラに怒られるのに快感を感じるようになったら立派なRust使いです。