RustのPinを完全に理解()するための31個のクイズ

RustのPinって難しいですよね。

ということで、Pinのお気持ちをどれだけ理解できているか測るためのクイズを用意しました。

簡単なやつからいきますので、安心してくださいね!

いきますよ〜!

Q0

これをコンパイル・実行するとどうなる?

fn main() {
    let a = 3;
    let b = 5;
    println!("(a, b) = ({a:?}, {b:?})");
    println!("(a, b) = ({a:?}, {b:?})");
}

こたえ

$ cargo --quiet run --example q0
(a, b) = (3, 5)
(a, b) = (3, 5)

解説

println!()の動作確認です。念の為2回実行しておきました(伏線)。

Q1

これをコンパイル・実行するとどうなる?

fn main() {
    let a = 3;
    let b = 5;
    println!("(a, b) = ({a:?}, {b:?})");
    core::mem::swap(&mut a, &mut b);
    println!("(a, b) = ({a:?}, {b:?})");
}

こたえ

$ cargo --quiet run --example q1
error[E0596]: cannot borrow `a` as mutable, as it is not declared as mutable
 --> examples/q1.rs:5:21
  |
5 |     core::mem::swap(&mut a, &mut b);
  |                     ^^^^^^ cannot borrow as mutable
  |
help: consider changing this to be mutable
  |
2 |     let mut a = 3;
  |         +++

error[E0596]: cannot borrow `b` as mutable, as it is not declared as mutable
 --> examples/q1.rs:5:29
  |
5 |     core::mem::swap(&mut a, &mut b);
  |                             ^^^^^^ cannot borrow as mutable
  |
help: consider changing this to be mutable
  |
3 |     let mut b = 5;
  |         +++

For more information about this error, try `rustc --explain E0596`.
error: could not compile `pinquiz` (example "q1") due to 2 previous errors

解説

core::mem::swap()は、引数で与えられた2つの&mut T型の参照がそれぞれ指しているT型の値を、それらをDropすることなく(つまりその値の生存期間を途切れさせることなく)メモリ上の位置を互いに交換する関数です。

あ、もちろん、&mutをとるためには変数がmutで宣言されている必要がありますから、コンパイルエラーになっているんですね。凡ミスです……。

Q2

これをコンパイル・実行するとどうなる?

fn main() {
    let mut a = 3;
    let mut b = 5;
    println!("(a, b) = ({a:?}, {b:?})");
    core::mem::swap(&mut a, &mut b);
    println!("(a, b) = ({a:?}, {b:?})");
}

こたえ

$ cargo --quiet run --example q2
(a, b) = (3, 5)
(a, b) = (5, 3)

解説

ということでmutをaとbの宣言時につけてあげたらいい感じに通りました。値もちゃんと入れ替わっていますね。想定通りです!

Q3

これをコンパイル・実行するとどうなる?

fn main() {
    let mut a = 3;
    let mut b = 5;
    println!("(a, b) @ ({0:p}, {1:p}) = ({0:?}, {1:?})", &mut a, &mut b);
    core::mem::swap(&mut a, &mut b);
    println!("(a, b) @ ({0:p}, {1:p}) = ({0:?}, {1:?})", &mut a, &mut b);
}

こたえ

$ cargo --quiet run --example q3
(a, b) @ (0x7ffc101d53d0, 0x7ffc101d53d4) = (3, 5)
(a, b) @ (0x7ffc101d53d0, 0x7ffc101d53d4) = (5, 3)

解説

値が入れ替わっても、それぞれの変数が占めるメモリ上の位置は変わりません。あくまでも、それぞれの値が入れ替わっただけです。

それを確かめるべく、各変数のアドレスを取得して表示しています。たしかにswapの前後でaとbのアドレスは変化していませんね。しかし、値だけが入れ替わっています。 つまり、aとbのメモリ上の位置は同一だけれども、その値だけが入れ替わったというわけです。

Q4

これをコンパイル・実行するとどうなる?

fn main() {
    let mut a = 3;
    let mut b = 5;
    let a = &mut a;
    let b = &mut b;
    println!("(a, b) @ ({0:p}, {1:p}) = ({0:?}, {1:?})", a, b);
    core::mem::swap(a, b);
    println!("(a, b) @ ({0:p}, {1:p}) = ({0:?}, {1:?})", a, b);
}

こたえ

$ cargo --quiet run --example q4
(a, b) @ (0x7ffee3f429c0, 0x7ffee3f429c4) = (3, 5)
(a, b) @ (0x7ffee3f429c0, 0x7ffee3f429c4) = (5, 3)

解説

なんか&mutっていっぱいあってだるかったので、変数に代入しておきました。これで何回も&mut &mutしなくて済みます。

ちなみに、Rustでは同じ名前の変数を同一スコープ内で再定義(let)でき、古いものは隠蔽(shadowing)されます。

c.f. https://doc.rust-lang.org/rust-by-example/variable_bindings/scope.html

私はこの仕様、好きです。行がチェーンで長くなりすぎたときに、letをいっぱい縦に並べることがあります。 あと、temporally value dropped 的なエラーが出るときは、コンパイラさんがそうすることをおすすめしてくれたりします。

まあ、これを許さない言語もいるので、初見だとびっくりするかもですが、慣れましょう!

Q5

これをコンパイル・実行するとどうなる?

fn main() {
    let mut a = 3;
    let mut b = 5;
    let a = &mut a;
    let b = &mut b;
    println!("(a, b) @ ({a:p}, {b:p}) = ({a:?}, {b:?})");
    core::mem::swap(a, b);
    println!("(a, b) @ ({a:p}, {b:p}) = ({a:?}, {b:?})");
}

こたえ

$ cargo --quiet run --example q5
(a, b) @ (0x7ffc887a82b0, 0x7ffc887a82b4) = (3, 5)
(a, b) @ (0x7ffc887a82b0, 0x7ffc887a82b4) = (5, 3)

解説

そういえばprintln!()の変数指定はそのスコープから見える変数名でも書けるので、書き換えときました。特に動作上は変化なしです。

Q6

これをコンパイル・実行するとどうなる?

use std::any::type_name_of_val;

fn main() {
    let mut a = 3u8;
    let mut b = 5u8;
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
    let a = &mut a;
    let b = &mut b;
    println!("(a, b) @ ({a:p}, {b:p}) = ({a:?}, {b:?})");
    core::mem::swap(a, b);
    println!("(a, b) @ ({a:p}, {b:p}) = ({a:?}, {b:?})");
}

こたえ

$ cargo --quiet run --example q6
a: u8
b: u8
(a, b) @ (0x7ffe385b3086, 0x7ffe385b3087) = (3, 5)
(a, b) @ (0x7ffe385b3086, 0x7ffe385b3087) = (5, 3)

解説

type_name_of_val()を使うと、その参照が指し示す値の型をコンパイル時文字列にできます。デバッグに便利です。 あと、aとbに代入する値をu8型である、と明確にsuffixで示しました。この結果、aとbのアドレスの差が、さきほどは4バイトだったのが1バイトになっていますね。 まあ、今のところはただの構文紹介です。

Q7

これをコンパイル・実行するとどうなる?

use std::any::type_name_of_val;

fn main() {
    let mut a = 3u8;
    let mut b = 5u8;
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
    let a = &mut a;
    let b = &mut b;
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
    println!("(a, b) @ ({a:p}, {b:p}) = ({a:?}, {b:?})");
    core::mem::swap(a, b);
    println!("(a, b) @ ({a:p}, {b:p}) = ({a:?}, {b:?})");
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
}

こたえ

$ cargo --quiet run --example q7
a: u8
b: u8
a: &mut u8
b: &mut u8
(a, b) @ (0x7fff4d30afa6, 0x7fff4d30afa7) = (3, 5)
(a, b) @ (0x7fff4d30afa6, 0x7fff4d30afa7) = (5, 3)
a: &mut u8
b: &mut u8

解説

一応参照をとった後とか、プログラムの最後のときの型も表示しておきました。ちゃんと参照になってますね。わかりやすい。

Q8

これをコンパイル・実行するとどうなる?

use std::any::type_name_of_val;
use std::pin::Pin;

fn main() {
    let mut a = 3u8;
    let mut b = 5u8;
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
    let a = &mut a;
    let b = &mut b;
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
    let a = Pin::new(a);
    let b = Pin::new(b);
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
    println!("(a, b) @ ({a:p}, {b:p}) = ({a:?}, {b:?})");
    core::mem::swap(a, b);
    println!("(a, b) @ ({a:p}, {b:p}) = ({a:?}, {b:?})");
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
}

こたえ

$ cargo --quiet run --example q8
error[E0308]: arguments to this function are incorrect
  --> examples/q8.rs:18:5
   |
18 |     core::mem::swap(a, b);
   |     ^^^^^^^^^^^^^^^
   |
note: expected `&mut _`, found `Pin<&mut u8>`
  --> examples/q8.rs:18:21
   |
18 |     core::mem::swap(a, b);
   |                     ^
   = note: expected mutable reference `&mut _`
                         found struct `Pin<&mut u8>`
note: expected `&mut _`, found `Pin<&mut u8>`
  --> examples/q8.rs:18:24
   |
18 |     core::mem::swap(a, b);
   |                        ^
   = note: expected mutable reference `&mut _`
                         found struct `Pin<&mut u8>`
note: function defined here
  --> /rustc/9fc6b43126469e3858e2fe86cafb4f0fd5068869/library/core/src/mem/mod.rs:732:14
help: consider mutably borrowing here
   |
18 |     core::mem::swap(&mut a, b);
   |                     ++++
help: consider mutably borrowing here
   |
18 |     core::mem::swap(a, &mut b);
   |                        ++++

For more information about this error, try `rustc --explain E0308`.
error: could not compile `pinquiz` (example "q8") due to 1 previous error

解説

さて本題のPinです。PinにはPin::newメソッドがあり、さらにPinはポインタに相当する型を内包するので、&mut u8にかぶせてみたらどうなるのかな?と雑に試した結果、コンパイラさんにだめって言われちゃいました。 えーっとね…あー、PinでくるんだPin<&mut u8>&mut なんとかの形の型ではないので、swapには使えないですよ〜って言われてますね。

でも、安心してください。Pinはポインタと同等の振る舞いをするインターフェイスがついているので、適当にそれを呼んであげればよさそうです。

こういうときは、borrow_mut()してあげれば、&mutな何かがもらえるはずです。やってみましょう。

Q9

これをコンパイル・実行するとどうなる?

use std::any::type_name_of_val;
use std::borrow::BorrowMut;
use std::pin::Pin;

fn main() {
    let mut a = 3u8;
    let mut b = 5u8;
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
    let a = &mut a;
    let b = &mut b;
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
    let a = Pin::new(a);
    let b = Pin::new(b);
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
    println!("(a, b) @ ({a:p}, {b:p}) = ({a:?}, {b:?})");
    core::mem::swap(a.borrow_mut(), b.borrow_mut());
    println!("(a, b) @ ({a:p}, {b:p}) = ({a:?}, {b:?})");
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
}

こたえ

$ cargo --quiet run --example q9
error[E0596]: cannot borrow `a` as mutable, as it is not declared as mutable
  --> examples/q9.rs:19:21
   |
19 |     core::mem::swap(a.borrow_mut(), b.borrow_mut());
   |                     ^ cannot borrow as mutable
   |
help: consider changing this to be mutable
   |
14 |     let mut a = Pin::new(a);
   |         +++

error[E0596]: cannot borrow `b` as mutable, as it is not declared as mutable
  --> examples/q9.rs:19:37
   |
19 |     core::mem::swap(a.borrow_mut(), b.borrow_mut());
   |                                     ^ cannot borrow as mutable
   |
help: consider changing this to be mutable
   |
15 |     let mut b = Pin::new(b);
   |         +++

For more information about this error, try `rustc --explain E0596`.
error: could not compile `pinquiz` (example "q9") due to 2 previous errors

解説

あー、おしい。borrow_mut()を呼ぶためには、Pin<&mut u8>を保持している変数がmutじゃないといけないんですね。つけてあげましょう!

Q10

これをコンパイル・実行するとどうなる?

use std::any::type_name_of_val;
use std::borrow::BorrowMut;
use std::pin::Pin;

fn main() {
    let mut a = 3u8;
    let mut b = 5u8;
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
    let a = &mut a;
    let b = &mut b;
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
    let mut a = Pin::new(a);
    let mut b = Pin::new(b);
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
    println!("(a, b) @ ({a:p}, {b:p}) = ({a:?}, {b:?})");
    core::mem::swap(a.borrow_mut(), b.borrow_mut());
    println!("(a, b) @ ({a:p}, {b:p}) = ({a:?}, {b:?})");
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
}

こたえ

$ cargo --quiet run --example q10
a: u8
b: u8
a: &mut u8
b: &mut u8
a: core::pin::Pin<&mut u8>
b: core::pin::Pin<&mut u8>
(a, b) @ (0x7ffeae1e3df6, 0x7ffeae1e3df7) = (3, 5)
(a, b) @ (0x7ffeae1e3df7, 0x7ffeae1e3df6) = (5, 3)
a: core::pin::Pin<&mut u8>
b: core::pin::Pin<&mut u8>

解説

おー、無事に通りました!これでめでたく、Pin<&mut u8>の中身が…え、入れ替わってるぅ…?というか、中身だけじゃなくて、変数自体も入れ替わってるよ…なんでぇ…?

…おちついてください、これはなにかの間違いです。どうして入れ替わっちゃったのか、明日までに考えてきてください。いいね?

Q11

これをコンパイル・実行するとどうなる?

use std::any::type_name_of_val;
use std::borrow::BorrowMut;
use std::pin::Pin;

fn main() {
    let mut a = 3u8;
    let mut b = 5u8;
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
    let a = &mut a;
    let b = &mut b;
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
    let mut a = Pin::new(a);
    let mut b = Pin::new(b);
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
    println!("(a, b) @ ({a:p}, {b:p}) = ({a:?}, {b:?})");
    core::mem::swap::<u8>(a.borrow_mut(), b.borrow_mut());
    println!("(a, b) @ ({a:p}, {b:p}) = ({a:?}, {b:?})");
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
}

こたえ

$ cargo --quiet run --example q11
a: u8
b: u8
a: &mut u8
b: &mut u8
a: core::pin::Pin<&mut u8>
b: core::pin::Pin<&mut u8>
(a, b) @ (0x7ffef9e58106, 0x7ffef9e58107) = (3, 5)
(a, b) @ (0x7ffef9e58106, 0x7ffef9e58107) = (5, 3)
a: core::pin::Pin<&mut u8>
b: core::pin::Pin<&mut u8>

解説

あー、なるほどね、swapの操作をu8に関して行うようにしたら、変数のアドレスは入れ変わらなくなりました。要するに、さきほどはPin<&mut u8>に関するswapをしていたので、ポインタ自体丸ごと入れ替わっちゃったんですね…borrow_mut()はどこの型についてmutableなborrowをするのか任意性があるので、こういうことが起きちゃうわけです。気をつけましょう。

…でも、やっぱり値は入れ替わっちゃいますね。Pinしてたら入れ替わらないんじゃなかったっけ…?

Q12

これをコンパイル・実行するとどうなる?

use std::any::type_name_of_val;
use std::borrow::BorrowMut;
use std::pin::Pin;

fn main() {
    let mut a = 3u8;
    let mut b = 5u8;
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
    let a = &mut a;
    let b = &mut b;
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
    let mut a = Pin::new(a);
    let mut b = Pin::new(b);
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
    println!("(a, b) @ ({a:p}, {b:p}) = ({a:?}, {b:?})");
    core::mem::swap::<Pin<&mut u8>>(a.borrow_mut(), b.borrow_mut());
    println!("(a, b) @ ({a:p}, {b:p}) = ({a:?}, {b:?})");
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
}

こたえ

$ cargo --quiet run --example q12
a: u8
b: u8
a: &mut u8
b: &mut u8
a: core::pin::Pin<&mut u8>
b: core::pin::Pin<&mut u8>
(a, b) @ (0x7fffcf215c96, 0x7fffcf215c97) = (3, 5)
(a, b) @ (0x7fffcf215c97, 0x7fffcf215c96) = (5, 3)
a: core::pin::Pin<&mut u8>
b: core::pin::Pin<&mut u8>

解説

これは先程の仮説の検証で、今度はu8ではなくPin<&mut>に関するswapをがんばってもらった図です。確かにswap時の型を指定しなかった場合と同様、アドレスレベルで入れ替わってますよね。

Q13

これをコンパイル・実行するとどうなる?

use std::any::type_name_of_val;
use std::borrow::BorrowMut;
use std::pin::Pin;

fn main() {
    let mut a = (3u8, ());
    let mut b = (5u8, ());
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
    let a = &mut a;
    let b = &mut b;
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
    let mut a = Pin::new(a);
    let mut b = Pin::new(b);
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
    println!("(a, b) @ ({a:p}, {b:p}) = ({a:?}, {b:?})");
    core::mem::swap::<(u8, ())>(a.borrow_mut(), b.borrow_mut());
    println!("(a, b) @ ({a:p}, {b:p}) = ({a:?}, {b:?})");
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
}

こたえ

$ cargo --quiet run --example q13
a: (u8, ())
b: (u8, ())
a: &mut (u8, ())
b: &mut (u8, ())
a: core::pin::Pin<&mut (u8, ())>
b: core::pin::Pin<&mut (u8, ())>
(a, b) @ (0x7ffd2079bf96, 0x7ffd2079bf97) = ((3, ()), (5, ()))
(a, b) @ (0x7ffd2079bf96, 0x7ffd2079bf97) = ((5, ()), (3, ()))
a: core::pin::Pin<&mut (u8, ())>
b: core::pin::Pin<&mut (u8, ())>

解説

さて、話を少し戻して、今度はPinの指す先をswapするように戻したうえで、u8型の代わりに(u8, ())型、つまりu8とunit typeのタプルを入れ替える操作をやってみました。

やっぱり通っちゃいますね…Pin…どうして…

Q14

これをコンパイル・実行するとどうなる?

use std::any::type_name_of_val;
use std::borrow::BorrowMut;
use std::pin::Pin;

fn main() {
    let mut a = (3u8, Default::default());
    let mut b = (5u8, Default::default());
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
    let a = &mut a;
    let b = &mut b;
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
    let mut a = Pin::new(a);
    let mut b = Pin::new(b);
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
    println!("(a, b) @ ({a:p}, {b:p}) = ({a:?}, {b:?})");
    core::mem::swap::<(u8, ())>(a.borrow_mut(), b.borrow_mut());
    println!("(a, b) @ ({a:p}, {b:p}) = ({a:?}, {b:?})");
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
}

こたえ

$ cargo --quiet run --example q14
a: (u8, ())
b: (u8, ())
a: &mut (u8, ())
b: &mut (u8, ())
a: core::pin::Pin<&mut (u8, ())>
b: core::pin::Pin<&mut (u8, ())>
(a, b) @ (0x7ffc42f7d676, 0x7ffc42f7d677) = ((3, ()), (5, ()))
(a, b) @ (0x7ffc42f7d676, 0x7ffc42f7d677) = ((5, ()), (3, ()))
a: core::pin::Pin<&mut (u8, ())>
b: core::pin::Pin<&mut (u8, ())>

解説

ここは小休止、Default::default()っていう便利なやつの紹介です。これは、Defaultトレイトのdefaultメソッドを呼び出すという意味になるので、書いた場所の型がDefaultを実装していたら、めでたくデフォルトの値を入れてくれるという便利な子です。ジェネリクスとかと相性がいいですよ。 unit typeの()のデフォルト値は()ですから(いいですか、前者が型の()で、後者が値の()です…よく見てください(大丈夫、同じ文字列です))

https://doc.rust-lang.org/std/primitive.unit.html

Q15

これをコンパイル・実行するとどうなる?

use std::any::type_name_of_val;
use std::borrow::BorrowMut;
use std::pin::Pin;

fn main() {
    type T = (u8, ());
    let mut a = (3u8, Default::default());
    let mut b = (5u8, Default::default());
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
    let a = &mut a;
    let b = &mut b;
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
    let mut a = Pin::new(a);
    let mut b = Pin::new(b);
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
    println!("(a, b) @ ({a:p}, {b:p}) = ({a:?}, {b:?})");
    core::mem::swap::<T>(a.borrow_mut(), b.borrow_mut());
    println!("(a, b) @ ({a:p}, {b:p}) = ({a:?}, {b:?})");
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
}

こたえ

$ cargo --quiet run --example q15
a: (u8, ())
b: (u8, ())
a: &mut (u8, ())
b: &mut (u8, ())
a: core::pin::Pin<&mut (u8, ())>
b: core::pin::Pin<&mut (u8, ())>
(a, b) @ (0x7ffcdaddd2c6, 0x7ffcdaddd2c7) = ((3, ()), (5, ()))
(a, b) @ (0x7ffcdaddd2c6, 0x7ffcdaddd2c7) = ((5, ()), (3, ()))
a: core::pin::Pin<&mut (u8, ())>
b: core::pin::Pin<&mut (u8, ())>

解説

これもリファクタリングで、(u8, ())って毎度書くのがだるいので、type T = ... という形で型の別名定義をしました。それ以上の深い理由はありません(ここで目をそらす…)

Q16

これをコンパイル・実行するとどうなる?

use std::any::type_name_of_val;
use std::borrow::BorrowMut;
use std::marker::PhantomPinned;
use std::pin::Pin;

fn main() {
    type T = (u8, PhantomPinned);
    let mut a = (3u8, Default::default());
    let mut b = (5u8, Default::default());
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
    let a = &mut a;
    let b = &mut b;
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
    let mut a = Pin::new(a);
    let mut b = Pin::new(b);
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
    println!("(a, b) @ ({a:p}, {b:p}) = ({a:?}, {b:?})");
    core::mem::swap::<T>(a.borrow_mut(), b.borrow_mut());
    println!("(a, b) @ ({a:p}, {b:p}) = ({a:?}, {b:?})");
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
}

こたえ

$ cargo --quiet run --example q16
error[E0277]: `PhantomPinned` cannot be unpinned
  --> examples/q16.rs:16:26
   |
16 |     let mut a = Pin::new(a);
   |                 -------- ^ within `(u8, PhantomPinned)`, the trait `Unpin` is not implemented for `PhantomPinned`
   |                 |
   |                 required by a bound introduced by this call
   |
   = note: consider using the `pin!` macro
           consider using `Box::pin` if you need to access the pinned value outside of the current scope
   = note: required because it appears within the type `(u8, PhantomPinned)`
note: required by a bound in `Pin::<Ptr>::new`
  --> /rustc/9fc6b43126469e3858e2fe86cafb4f0fd5068869/library/core/src/pin.rs:1191:5

error[E0277]: `PhantomPinned` cannot be unpinned
  --> examples/q16.rs:17:26
   |
17 |     let mut b = Pin::new(b);
   |                 -------- ^ within `(u8, PhantomPinned)`, the trait `Unpin` is not implemented for `PhantomPinned`
   |                 |
   |                 required by a bound introduced by this call
   |
   = note: consider using the `pin!` macro
           consider using `Box::pin` if you need to access the pinned value outside of the current scope
   = note: required because it appears within the type `(u8, PhantomPinned)`
note: required by a bound in `Pin::<Ptr>::new`
  --> /rustc/9fc6b43126469e3858e2fe86cafb4f0fd5068869/library/core/src/pin.rs:1191:5

For more information about this error, try `rustc --explain E0277`.
error: could not compile `pinquiz` (example "q16") due to 2 previous errors

解説

すいません、伏線でした。いままでタプルの右側の型は()でしたが、今度はPhantomPinnedとかいう謎の型になっています。

https://doc.rust-lang.org/std/marker/struct.PhantomPinned.html

こいつがPinの世界における鍵のひとつです。…ひとつであって全てではありません。悲しいね! まあ端的にいうと、この子はUnpinトレイトを実装していない型です。(否定の否定、頭がこんがらかるね、よくない…)

https://doc.rust-lang.org/std/marker/trait.Unpin.html

まあ詳細は省きますが、上のエラーが言っていることを解説すると、Pin::newはUnpinを実装している型を引数にとるが、いま渡されている値はUnpinを実装しない型だから呼び出せないぞ!っていうことです。

実際、Pin::newの定義を見に行くと、そう書いてあります。

https://doc.rust-lang.org/src/core/pin.rs.html#1169

impl<Ptr: Deref<Target: Unpin>> Pin<Ptr> {
...
    pub const fn new(pointer: Ptr) -> Pin<Ptr> {
        // SAFETY: the value pointed to is `Unpin`, and so has no requirements
        // around pinning.
        unsafe { Pin::new_unchecked(pointer) }
    }

ね?じゃあなぜ今までエラーが出てなかったのかというと、Unpinトレイトってやつが自動的に実装されるやばいトレイトだからです。

…まあ別にやばくはないんですが、基本的にどの型も構造体も、その内部にUnpinトレイトを実装しない型が含まれていなければ、Unpinトレイトが実装されるのです。

…えーっと、言い換えれば、ある型のすべてのメンバがUnpinトレイトを実装していれば、そのようなメンバからなる型もUnpinトレイトが実装されるのです。

そう、あなたがタプルを作ったりstructを作ったりしているとき、たいていのばあい、Unpinトレイトが実装されるのです。気づいていましたか?(通常は気にしなくていい。通常は、ね…)

ま、結論をいうと、Unpinトレイトが実装されていない奇妙な型 PhantomPinnedを含むようにタプルを書き換えた結果、そのタプルもUnpinトレイトが実装されなくなってしまい、Pin::new()を呼び出す資格なしとコンパイラさんに言われてしまったわけです。でも、どうしてPinはそんな制限をかけてるんですかねえ…(すっとぼけ)

まあいいや、なんかコンパイラさん曰く"note: consider using the pin! macro", 要するにpin!()マクロ使えってことらしいので、それ使ってみましょう!

(よい子のみんなは、コンパイラさんの言っていることを素直に受け止めるのもいいけれど、ドキュメントもちらっと見ておくといいぞ!今回は話の都合上、私は素通りしますが…(だめですよ!))

https://doc.rust-lang.org/std/pin/macro.pin.html

Q17

これをコンパイル・実行するとどうなる?

use std::any::type_name_of_val;
use std::borrow::BorrowMut;
use std::marker::PhantomPinned;
use std::pin::pin;

fn main() {
    type T = (u8, PhantomPinned);
    let mut a = (3u8, Default::default());
    let mut b = (5u8, Default::default());
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
    let a = &mut a;
    let b = &mut b;
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
    let mut a = pin!(a);
    let mut b = pin!(b);
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
    println!("(a, b) @ ({a:p}, {b:p}) = ({a:?}, {b:?})");
    core::mem::swap::<T>(a.borrow_mut(), b.borrow_mut());
    println!("(a, b) @ ({a:p}, {b:p}) = ({a:?}, {b:?})");
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
}

こたえ

$ cargo --quiet run --example q17
a: (u8, core::marker::PhantomPinned)
b: (u8, core::marker::PhantomPinned)
a: &mut (u8, core::marker::PhantomPinned)
b: &mut (u8, core::marker::PhantomPinned)
a: core::pin::Pin<&mut &mut (u8, core::marker::PhantomPinned)>
b: core::pin::Pin<&mut &mut (u8, core::marker::PhantomPinned)>
(a, b) @ (0x7fff54dd76f0, 0x7fff54dd7700) = ((3, PhantomPinned), (5, PhantomPinned))
(a, b) @ (0x7fff54dd76f0, 0x7fff54dd7700) = ((5, PhantomPinned), (3, PhantomPinned))
a: core::pin::Pin<&mut &mut (u8, core::marker::PhantomPinned)>
b: core::pin::Pin<&mut &mut (u8, core::marker::PhantomPinned)>

解説

やった〜!コンパイル通った〜!なんかpin!()マクロをPin::new()の代わりに使ったら通っちゃった〜

…いや、通らないでほしかったんだけど…なんで…

あ、型を冷静に見てみましょう。core::pin::Pin<&mut &mut (u8, core::marker::PhantomPinned)>とかいう奇妙な型になってますね。二重の参照をPinでくるんでいるという…なかなかワイルドな型です。

そうか、pin!()マクロに渡すのは参照ではなく、Pinが指すべき値を突っ込むべきなんですね〜おーけー、じゃあ&mutとってる部分消しましょか…

Q18

これをコンパイル・実行するとどうなる?

use std::any::type_name_of_val;
use std::borrow::BorrowMut;
use std::marker::PhantomPinned;
use std::pin::pin;

fn main() {
    type T = (u8, PhantomPinned);
    let a = (3u8, Default::default());
    let b = (5u8, Default::default());
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
    let mut a = pin!(a);
    let mut b = pin!(b);
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
    println!("(a, b) @ ({a:p}, {b:p}) = ({a:?}, {b:?})");
    core::mem::swap::<T>(a.borrow_mut(), b.borrow_mut());
    println!("(a, b) @ ({a:p}, {b:p}) = ({a:?}, {b:?})");
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
}

こたえ

$ cargo --quiet run --example q18
error[E0596]: cannot borrow data in dereference of `Pin<&mut (u8, PhantomPinned)>` as mutable
  --> examples/q18.rs:17:26
   |
17 |     core::mem::swap::<T>(a.borrow_mut(), b.borrow_mut());
   |                          ^^^^^^^^^^^^^^ cannot borrow as mutable
   |
   = help: trait `DerefMut` is required to modify through a dereference, but it is not implemented for `Pin<&mut (u8, PhantomPinned)>`

error[E0596]: cannot borrow data in dereference of `Pin<&mut (u8, PhantomPinned)>` as mutable
  --> examples/q18.rs:17:42
   |
17 |     core::mem::swap::<T>(a.borrow_mut(), b.borrow_mut());
   |                                          ^^^^^^^^^^^^^^ cannot borrow as mutable
   |
   = help: trait `DerefMut` is required to modify through a dereference, but it is not implemented for `Pin<&mut (u8, PhantomPinned)>`

For more information about this error, try `rustc --explain E0596`.
error: could not compile `pinquiz` (example "q18") due to 2 previous errors

解説

消したらまたコンパイラさんがだめだって言ってます。にゃーん…。

えっと、DerefMutがPin<&mut(u8, PhantomPinned)>にないって言われちゃいました。えー…さっきは似たようなやつで通ってたのに…

いえ、これで正しいんです。Pin型にDerefMutが実装される条件は、

impl<Ptr> DerefMut for Pin<Ptr>
where
    Ptr: DerefMut,
    <Ptr as Deref>::Target: Unpin,

というわけで、Pin<Ptr>の<Ptr as Deref>::TargetがUnpinを実装している場合のみなので、PhantomPinnedさんが入ってきてUnpinトレイトが実装されなくなったので、私達はもうDerefMutを呼び出す資格を失ってしまったのです。

これこそが、Pinがいかに値のmoveを抑制するのか、の具体例です。Unpinトレイトを実装していない型を指すようなポインタ型PtrをくるむPin型があったとき、その内部への可変参照を取得できないようにしてあげることで、swapなどをはじめとした、値のmoveを行いかねない行動をunsafeなものとして制限しているのです。

Q19

これをコンパイル・実行するとどうなる?

use std::any::type_name_of_val;
use std::marker::PhantomPinned;
use std::pin::pin;

fn main() {
    type T = (u8, PhantomPinned);
    let a: T = (3u8, Default::default());
    let b: T = (5u8, Default::default());
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
    let a = pin!(a);
    let b = pin!(b);
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
    println!("(a, b) @ ({a:p}, {b:p}) = ({a:?}, {b:?})");
    //core::mem::swap::<T>(a.borrow_mut(), b.borrow_mut());
    println!("(a, b) @ ({a:p}, {b:p}) = ({a:?}, {b:?})");
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
}

こたえ

$ cargo --quiet run --example q19
a: (u8, core::marker::PhantomPinned)
b: (u8, core::marker::PhantomPinned)
a: core::pin::Pin<&mut (u8, core::marker::PhantomPinned)>
b: core::pin::Pin<&mut (u8, core::marker::PhantomPinned)>
(a, b) @ (0x7ffce402b167, 0x7ffce402b177) = ((3, PhantomPinned), (5, PhantomPinned))
(a, b) @ (0x7ffce402b167, 0x7ffce402b177) = ((3, PhantomPinned), (5, PhantomPinned))
a: core::pin::Pin<&mut (u8, core::marker::PhantomPinned)>
b: core::pin::Pin<&mut (u8, core::marker::PhantomPinned)>

解説

そういうわけで、ひとつ前のソースコードのswapをコメントアウトしてあげれば、このコードはコンパイルできるようになります。

可変参照を防げたバンザイ!Pinで値の安住の地が約束された!やった〜

…安住の地を得るのはこんなにも難しいものなのです。ええ。Unpinを外し忘れると、余裕で引っ越しを迫られるわけです。気をつけましょうね…。

Q20

これをコンパイル・実行するとどうなる?

use std::any::type_name_of_val;
use std::borrow::BorrowMut;
use std::marker::PhantomPinned;
use std::pin::pin;

fn main() {
    type T = (u8, PhantomPinned);
    let a: T = (3u8, Default::default());
    let b: T = (5u8, Default::default());
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
    let mut a = pin!(a);
    let mut b = pin!(b);
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
    println!("(a, b) @ ({a:p}, {b:p}) = ({a:?}, {b:?})");
    core::mem::swap::<T>(a.borrow_mut(), b.borrow_mut());
    println!("(a, b) @ ({a:p}, {b:p}) = ({a:?}, {b:?})");
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
}

こたえ

$ cargo --quiet run --example q20
error[E0596]: cannot borrow data in dereference of `Pin<&mut (u8, PhantomPinned)>` as mutable
  --> examples/q20.rs:17:26
   |
17 |     core::mem::swap::<T>(a.borrow_mut(), b.borrow_mut());
   |                          ^^^^^^^^^^^^^^ cannot borrow as mutable
   |
   = help: trait `DerefMut` is required to modify through a dereference, but it is not implemented for `Pin<&mut (u8, PhantomPinned)>`

error[E0596]: cannot borrow data in dereference of `Pin<&mut (u8, PhantomPinned)>` as mutable
  --> examples/q20.rs:17:42
   |
17 |     core::mem::swap::<T>(a.borrow_mut(), b.borrow_mut());
   |                                          ^^^^^^^^^^^^^^ cannot borrow as mutable
   |
   = help: trait `DerefMut` is required to modify through a dereference, but it is not implemented for `Pin<&mut (u8, PhantomPinned)>`

For more information about this error, try `rustc --explain E0596`.
error: could not compile `pinquiz` (example "q20") due to 2 previous errors

解説

今一度、Pinがswapの立ち退き要求を跳ね返す様をご覧にいれましょう。よく目に焼き付けておいてくださいね…。

Q21

これをコンパイル・実行するとどうなる?

use std::any::type_name_of_val;
use std::borrow::BorrowMut;
use std::pin::pin;

fn main() {
    type T = (u8, ());
    let a: T = (3u8, Default::default());
    let b: T = (5u8, Default::default());
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
    let mut a = pin!(a);
    let mut b = pin!(b);
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
    println!("(a, b) @ ({a:p}, {b:p}) = ({a:?}, {b:?})");
    core::mem::swap::<T>(a.borrow_mut(), b.borrow_mut());
    println!("(a, b) @ ({a:p}, {b:p}) = ({a:?}, {b:?})");
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
}

こたえ

$ cargo --quiet run --example q21
a: (u8, ())
b: (u8, ())
a: core::pin::Pin<&mut (u8, ())>
b: core::pin::Pin<&mut (u8, ())>
(a, b) @ (0x7fff4131c4b7, 0x7fff4131c4c7) = ((3, ()), (5, ()))
(a, b) @ (0x7fff4131c4b7, 0x7fff4131c4c7) = ((5, ()), (3, ()))
a: core::pin::Pin<&mut (u8, ())>
b: core::pin::Pin<&mut (u8, ())>

解説

復習ですが、PhantomPinnedを()に書き換えると、確かにコンパイルが通ってswapできてしまいます。PhantomPinnedはだいじに身に着けておきましょう。

Q22

これをコンパイル・実行するとどうなる?

use std::any::type_name_of_val;
use std::borrow::BorrowMut;
use std::pin::pin;

fn main() {
    type T = u8;
    let a: T = 3u8;
    let b: T = 5u8;
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
    let mut a = pin!(a);
    let mut b = pin!(b);
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
    println!("(a, b) @ ({a:p}, {b:p}) = ({a:?}, {b:?})");
    core::mem::swap::<T>(a.borrow_mut(), b.borrow_mut());
    println!("(a, b) @ ({a:p}, {b:p}) = ({a:?}, {b:?})");
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
}

こたえ

$ cargo --quiet run --example q22
a: u8
b: u8
a: core::pin::Pin<&mut u8>
b: core::pin::Pin<&mut u8>
(a, b) @ (0x7ffca8478dd7, 0x7ffca8478de7) = (3, 5)
(a, b) @ (0x7ffca8478dd7, 0x7ffca8478de7) = (5, 3)
a: core::pin::Pin<&mut u8>
b: core::pin::Pin<&mut u8>

解説

そういえばしばらくタプルで説明していましたが、もちろんu8そのままの値でも同様の挙動になります。再確認です。

Q23

これをコンパイル・実行するとどうなる?

use std::any::type_name_of_val;
use std::borrow::BorrowMut;
use std::pin::pin;

fn main() {
    type T = Box<u8>;
    let a: T = Box::new(3u8);
    let b: T = Box::new(5u8);
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
    let mut a = pin!(a);
    let mut b = pin!(b);
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
    println!("(a, b) @ ({a:p}, {b:p}) = ({a:?}, {b:?})");
    core::mem::swap::<T>(a.borrow_mut(), b.borrow_mut());
    println!("(a, b) @ ({a:p}, {b:p}) = ({a:?}, {b:?})");
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
}

こたえ

$ cargo --quiet run --example q23
a: alloc::boxed::Box<u8>
b: alloc::boxed::Box<u8>
a: core::pin::Pin<&mut alloc::boxed::Box<u8>>
b: core::pin::Pin<&mut alloc::boxed::Box<u8>>
(a, b) @ (0x7ffdbbe735d8, 0x7ffdbbe735e8) = (3, 5)
(a, b) @ (0x7ffdbbe735d8, 0x7ffdbbe735e8) = (5, 3)
a: core::pin::Pin<&mut alloc::boxed::Box<u8>>
b: core::pin::Pin<&mut alloc::boxed::Box<u8>>

解説

さて、&mutをPinでくるんだときの挙動はわかりましたが、Pinがくるむことのできる「ポインタ型」は&や&mutに限りません。 Boxという、ヒープ上にメモリを確保してそこに対するポインタを保持する型も、Pinでくるむことができます。

ただ、雑にpin!()マクロにBox型を渡してしまうと、&mutが余計についてしまいます。これでは、Pinの効果が発揮できません。

Q24

これをコンパイル・実行するとどうなる?

use std::any::type_name_of_val;
use std::borrow::BorrowMut;
use std::marker::PhantomPinned;
use std::pin::pin;

fn main() {
    type T = Box<(u8, PhantomPinned)>;
    let a: T = Box::new((3u8, Default::default()));
    let b: T = Box::new((5u8, Default::default()));
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
    let mut a = pin!(a);
    let mut b = pin!(b);
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
    println!("(a, b) @ ({a:p}, {b:p}) = ({a:?}, {b:?})");
    core::mem::swap::<T>(a.borrow_mut(), b.borrow_mut());
    println!("(a, b) @ ({a:p}, {b:p}) = ({a:?}, {b:?})");
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
}

こたえ

$ cargo --quiet run --example q24
a: alloc::boxed::Box<(u8, core::marker::PhantomPinned)>
b: alloc::boxed::Box<(u8, core::marker::PhantomPinned)>
a: core::pin::Pin<&mut alloc::boxed::Box<(u8, core::marker::PhantomPinned)>>
b: core::pin::Pin<&mut alloc::boxed::Box<(u8, core::marker::PhantomPinned)>>
(a, b) @ (0x7ffc58ed5198, 0x7ffc58ed51a8) = ((3, PhantomPinned), (5, PhantomPinned))
(a, b) @ (0x7ffc58ed5198, 0x7ffc58ed51a8) = ((5, PhantomPinned), (3, PhantomPinned))
a: core::pin::Pin<&mut alloc::boxed::Box<(u8, core::marker::PhantomPinned)>>
b: core::pin::Pin<&mut alloc::boxed::Box<(u8, core::marker::PhantomPinned)>>

解説

実際、この状況で()をPhantomPinnedに書き換えても、Pinの効果は発動しません。これは、core::pin::Pin<&mut alloc::boxed::Box<(u8, core::marker::PhantomPinned)>>を注意深く見ていただければわかるのですが、Pin<ポインタ型<ポインタ型<(値)>>>という構造になっているため、Pinでくるまれた直下のポインタのTarget(=alloc::boxed::Box<(u8, core::marker::PhantomPinned)>)はUnpinを実装している型であるために、Pinの保護効果が発生しないのです。だから、Boxに対してpinをしたいときは、コンパイルが通るからと言ってBoxしてpin!するのではなく、Box::pin()という専用のメソッドがあるので、そちらを利用すると&mutが挟まるのを回避でき、めでたくPin直下のポインタのTarget(=(u8, core::marker::PhantomPinned))がUnpinを実装していない型になるので、うまく効果が発動するようになります(次の問題を参照)

Q25

これをコンパイル・実行するとどうなる?

use std::any::type_name_of_val;
use std::borrow::BorrowMut;
use std::marker::PhantomPinned;
use std::pin::Pin;

fn main() {
    type T = (u8, PhantomPinned);
    let mut a: Pin<Box<T>> = Box::pin((3u8, Default::default()));
    let mut b: Pin<Box<T>> = Box::pin((5u8, Default::default()));
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
    println!("(a, b) @ ({a:p}, {b:p}) = ({a:?}, {b:?})");
    core::mem::swap::<T>(a.borrow_mut(), b.borrow_mut());
    println!("(a, b) @ ({a:p}, {b:p}) = ({a:?}, {b:?})");
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
}

こたえ

$ cargo --quiet run --example q25
error[E0596]: cannot borrow data in dereference of `Pin<Box<(u8, PhantomPinned)>>` as mutable
  --> examples/q25.rs:13:26
   |
13 |     core::mem::swap::<T>(a.borrow_mut(), b.borrow_mut());
   |                          ^^^^^^^^^^^^^^ cannot borrow as mutable
   |
   = help: trait `DerefMut` is required to modify through a dereference, but it is not implemented for `Pin<Box<(u8, PhantomPinned)>>`

error[E0596]: cannot borrow data in dereference of `Pin<Box<(u8, PhantomPinned)>>` as mutable
  --> examples/q25.rs:13:42
   |
13 |     core::mem::swap::<T>(a.borrow_mut(), b.borrow_mut());
   |                                          ^^^^^^^^^^^^^^ cannot borrow as mutable
   |
   = help: trait `DerefMut` is required to modify through a dereference, but it is not implemented for `Pin<Box<(u8, PhantomPinned)>>`

For more information about this error, try `rustc --explain E0596`.
error: could not compile `pinquiz` (example "q25") due to 2 previous errors

解説

ほら、DerefMutが実装されてないよ〜と言われて弾かれています。正しく使えば正しくコンパイルエラーにしてくれるんです、Pinは。

Q26

これをコンパイル・実行するとどうなる?

use std::any::type_name_of_val;
use std::borrow::BorrowMut;
use std::pin::Pin;

fn main() {
    type T = (u8, ());
    let mut a: Pin<Box<T>> = Box::pin((3u8, Default::default()));
    let mut b: Pin<Box<T>> = Box::pin((5u8, Default::default()));
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
    println!("(a, b) @ ({a:p}, {b:p}) = ({a:?}, {b:?})");
    core::mem::swap::<T>(a.borrow_mut(), b.borrow_mut());
    println!("(a, b) @ ({a:p}, {b:p}) = ({a:?}, {b:?})");
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
}

こたえ

$ cargo --quiet run --example q26
a: core::pin::Pin<alloc::boxed::Box<(u8, ())>>
b: core::pin::Pin<alloc::boxed::Box<(u8, ())>>
(a, b) @ (0x562a3fab3b10, 0x562a3fab3b30) = ((3, ()), (5, ()))
(a, b) @ (0x562a3fab3b10, 0x562a3fab3b30) = ((5, ()), (3, ()))
a: core::pin::Pin<alloc::boxed::Box<(u8, ())>>
b: core::pin::Pin<alloc::boxed::Box<(u8, ())>>

解説

もちろん、Boxを使っていても、Boxの指す先がUnpinを実装している型だったら、DerefMutできちゃいますからね。気をつけてください。

…やっぱりPinって名前が悪いよ!PinPtrTargetIfItDoesNotImplUnpinとかにしたほうがいいって…(Objective-C的長い名前推進過激派)

Q27

これをコンパイル・実行するとどうなる?

use std::any::type_name_of_val;
use std::borrow::BorrowMut;
use std::pin::Pin;

#[derive(Debug)]
#[allow(unused)]
struct Wrapper(u8, ());

fn main() {
    type T = Wrapper;
    let mut a: Pin<Box<T>> = Box::pin(Wrapper(3u8, Default::default()));
    let mut b: Pin<Box<T>> = Box::pin(Wrapper(5u8, Default::default()));
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
    println!("(a, b) @ ({a:p}, {b:p}) = ({a:?}, {b:?})");
    core::mem::swap::<T>(a.borrow_mut(), b.borrow_mut());
    println!("(a, b) @ ({a:p}, {b:p}) = ({a:?}, {b:?})");
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
}

こたえ

$ cargo --quiet run --example q27
a: core::pin::Pin<alloc::boxed::Box<q27::Wrapper>>
b: core::pin::Pin<alloc::boxed::Box<q27::Wrapper>>
(a, b) @ (0x55e937e0ab10, 0x55e937e0ab30) = (Wrapper(3, ()), Wrapper(5, ()))
(a, b) @ (0x55e937e0ab10, 0x55e937e0ab30) = (Wrapper(5, ()), Wrapper(3, ()))
a: core::pin::Pin<alloc::boxed::Box<q27::Wrapper>>
b: core::pin::Pin<alloc::boxed::Box<q27::Wrapper>>

解説

あと、メンバが全員Unpinトレイトを実装している場合にUnpinトレイトが自動実装されるよって話は、自前でstructを定義した際にももちろん適用されます。Unpin by default, いいね?(つまり、Pin is no op by default, いいね?(つらい))

Q28

これをコンパイル・実行するとどうなる?

use std::any::type_name_of_val;
use std::borrow::BorrowMut;
use std::ops::Drop;
use std::pin::Pin;

#[derive(Debug)]
#[allow(unused)]
struct Wrapper(u8, ());
impl Drop for Wrapper {
    fn drop(&mut self) {
        println!("dropped: {self:?} @ {self:p}")
    }
}

fn main() {
    type T = Wrapper;
    let mut a: Pin<Box<T>> = Box::pin(Wrapper(3u8, Default::default()));
    let mut b: Pin<Box<T>> = Box::pin(Wrapper(5u8, Default::default()));
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
    println!("(a, b) @ ({a:p}, {b:p}) = ({a:?}, {b:?})");
    core::mem::swap::<T>(a.borrow_mut(), b.borrow_mut());
    println!("(a, b) @ ({a:p}, {b:p}) = ({a:?}, {b:?})");
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
}

こたえ

$ cargo --quiet run --example q28
a: core::pin::Pin<alloc::boxed::Box<q28::Wrapper>>
b: core::pin::Pin<alloc::boxed::Box<q28::Wrapper>>
(a, b) @ (0x55e69849fb10, 0x55e69849fb30) = (Wrapper(3, ()), Wrapper(5, ()))
(a, b) @ (0x55e69849fb10, 0x55e69849fb30) = (Wrapper(5, ()), Wrapper(3, ()))
a: core::pin::Pin<alloc::boxed::Box<q28::Wrapper>>
b: core::pin::Pin<alloc::boxed::Box<q28::Wrapper>>
dropped: Wrapper(3, ()) @ 0x55e69849fb30
dropped: Wrapper(5, ()) @ 0x55e69849fb10

解説

ちなみにこうやって自分で構造体にDropを実装してあげれば、その値の生存期間を知ることができるわけですが、swapはメモリ上を移動するのに生存期間は終わらないんですよ。まあ、そういうことになっているからそうなんですが、そうなんですよ。変数が生存しているからと言って、ずっと同じメモリアドレスにいると思ったら大間違いなんですね。みなさんも目が覚めたら突然知らない天井を見上げることになるかもしれないんです。多くの人はそんな可能性を検討していないかもしれませんが。気をつけてね!

Q29

これをコンパイル・実行するとどうなる?

use std::any::type_name_of_val;
use std::borrow::BorrowMut;
use std::marker::PhantomPinned;
use std::ops::Drop;
use std::pin::Pin;

#[derive(Debug)]
#[allow(unused)]
struct Wrapper(u8, PhantomPinned);
impl Drop for Wrapper {
    fn drop(&mut self) {
        println!("dropped: {self:?} @ {self:p}")
    }
}

fn main() {
    type T = Wrapper;
    let mut a: Pin<Box<T>> = Box::pin(Wrapper(3u8, Default::default()));
    let mut b: Pin<Box<T>> = Box::pin(Wrapper(5u8, Default::default()));
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
    println!("(a, b) @ ({a:p}, {b:p}) = ({a:?}, {b:?})");
    core::mem::swap::<T>(a.borrow_mut(), b.borrow_mut());
    println!("(a, b) @ ({a:p}, {b:p}) = ({a:?}, {b:?})");
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
}

こたえ

$ cargo --quiet run --example q29
error[E0596]: cannot borrow data in dereference of `Pin<Box<Wrapper>>` as mutable
  --> examples/q29.rs:23:26
   |
23 |     core::mem::swap::<T>(a.borrow_mut(), b.borrow_mut());
   |                          ^^^^^^^^^^^^^^ cannot borrow as mutable
   |
   = help: trait `DerefMut` is required to modify through a dereference, but it is not implemented for `Pin<Box<Wrapper>>`

error[E0596]: cannot borrow data in dereference of `Pin<Box<Wrapper>>` as mutable
  --> examples/q29.rs:23:42
   |
23 |     core::mem::swap::<T>(a.borrow_mut(), b.borrow_mut());
   |                                          ^^^^^^^^^^^^^^ cannot borrow as mutable
   |
   = help: trait `DerefMut` is required to modify through a dereference, but it is not implemented for `Pin<Box<Wrapper>>`

For more information about this error, try `rustc --explain E0596`.
error: could not compile `pinquiz` (example "q29") due to 2 previous errors

解説

もちろん、自前で定義した構造体のメンバの一部がUnpinを実装しない子(e.g. PhantomPinned)だったら、Unpinは実装されません。なので、PinしたいオブジェクトにはPhantomPinnedを含めてあげればよいです。

…でもね、

Q30

これをコンパイル・実行するとどうなる?

use std::any::type_name_of_val;
use std::borrow::BorrowMut;
use std::marker::PhantomPinned;
use std::ops::Drop;
use std::pin::Pin;

#[derive(Debug)]
#[allow(unused)]
struct Wrapper(u8, PhantomPinned);
impl Drop for Wrapper {
    fn drop(&mut self) {
        println!("dropped: {self:?} @ {self:p}")
    }
}
impl Unpin for Wrapper {}

fn main() {
    type T = Wrapper;
    let mut a: Pin<Box<T>> = Box::pin(Wrapper(3u8, Default::default()));
    let mut b: Pin<Box<T>> = Box::pin(Wrapper(5u8, Default::default()));
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
    println!("(a, b) @ ({a:p}, {b:p}) = ({a:?}, {b:?})");
    core::mem::swap::<T>(a.borrow_mut(), b.borrow_mut());
    println!("(a, b) @ ({a:p}, {b:p}) = ({a:?}, {b:?})");
    println!("a: {}", type_name_of_val(&a));
    println!("b: {}", type_name_of_val(&b));
}

こたえ

$ cargo --quiet run --example q30
a: core::pin::Pin<alloc::boxed::Box<q30::Wrapper>>
b: core::pin::Pin<alloc::boxed::Box<q30::Wrapper>>
(a, b) @ (0x556d228dcb10, 0x556d228dcb30) = (Wrapper(3, PhantomPinned), Wrapper(5, PhantomPinned))
(a, b) @ (0x556d228dcb10, 0x556d228dcb30) = (Wrapper(5, PhantomPinned), Wrapper(3, PhantomPinned))
a: core::pin::Pin<alloc::boxed::Box<q30::Wrapper>>
b: core::pin::Pin<alloc::boxed::Box<q30::Wrapper>>
dropped: Wrapper(3, PhantomPinned) @ 0x556d228dcb30
dropped: Wrapper(5, PhantomPinned) @ 0x556d228dcb10

解説

impl Unpin for Wrapper {}ってやるのはsafeなんですよ。これをやってしまえば、その中にPhantomPinnedがいようがいまいが、Pinの保護機構は働かないのです。

あとがき

こんなことをしている場合ではなかった。でも、Pinがその名前やドキュメント上の解説からは読み取りづらい挙動をしているのは事実で、しかも基本的に非常によくできていて過去の失敗をいい感じに回避してきたRustという言語の中でも、互換性とか歴史的事情とか諸々の要因でこんな辛い部分があるのは、現実ってしょっぱいね、という気持ちを新たにさせてくれる、いい機会だったと思う。

もうちょっと頭の中が整理されたら、ドキュメントとか、もしくはPin自体の改善提案をできたらいいなあと思いつつ、勢いで書き上げてしまったのでインターネットに放流します。 積んでるタスクが片付いたら、また戻ってきたいですね。

ということで、Pinは用法用量を守って正しくお使いください。

参考

github.com

github.com

www.youtube.com

2024年の振り返りと2025年の抱負

今年も例年通り、この一年の自分のTLを見返してまとめることにしよう。

総評

疲れてるなら休め。我慢をするな。直感を信じろ。

判断をするのはそれからでも間に合う。

カテゴリ別イベントログ

  • 休職(8月~)
  • OS自作(いつもの)
    • 基本的なHTTPクライアントが動く(9月)
    • d0iasmさんのブラウザが動く(9月)
    • QEMUのバグ潰し(3月, 5月)
  • 少しの遠出・参加イベント
    • 大都会岡山(2月)
    • STEP講師(5月~6月)
    • セキュキャンOS自作ゼミ(8月)
    • ラボユース合宿(8月)
    • 京都APSys聴講(9月)
    • 情報科学若手の会(9月)
  • Binary Hacks Rebooted (8月刊行)
    • およびその販促トークイベント(8月)
  • ハードウエア遊び
    • 洗濯機分解清掃
    • M5Stamp Fly
    • 3Dプリンタ遊び
    • 雑な除湿機自作
    • 4台目の3Dプリンタお迎え
    • コンセント増設(11月)
  • その他
    • 第二種電気工事士免状を取得(11月)
    • d0iasm氏の「[作って学ぶ]ブラウザ」が出版された(11月)
      • このブラウザは私の自作OSの上で動く
    • 技術書とポケモンとガジェットを大量にお迎え(通年)
    • 新型コロナ陽性(4月)
    • 相変わらずのレイオフや給料据え置き・減額祭り(2月, 3月)
    • Chromeちゃん爆誕(6月)
    • 肉・パンケーキ・甘いもの・かわいいなどを摂取

月ごとの投稿抜粋

量が多いですが、時系列まとめです。

1月

今週 今年の目標(に実質的になってしまった…)(でも実現したのでえらい!)

一年の計は分解から(古い床拭き掃除機のブラーバです)

Milk-V Pioneerちゃんをお迎え(あまり活用できてないので使わなければ…)

これ、全然できていませんでした。おそらくあと52年くらいかかります(今週の目標が今年の目標になったことからの類推)

この頃は、時刻に脳が支配されていました…(虚ろな目)

切り抜かれた配信。asusn_onlineさん、ありがとうございます!!!

頭のネジ、いまは壊れてます(締め過ぎたら折れた)(また印刷しなきゃ…)

"Don't forget why you got into this business in the first place: Science is fun. Minimize the noise that causes anxiety"

Referring to VMCS may be sensitive (?)

SFP+の載ったボード、まだちゃんと活用できてない。活用しなきゃ…

栓抜きがなかったので石川県を印刷した(支離滅裂な発言・思考)

ちゃんと使えます(今も渋谷拠点の冷蔵庫に入ってます)

会社は人間を切るのをやめろ

まだ半分くらいしか読めてない(まあでもスタックに積んであるのでOK)

2月

会社は人間を切るのをやめろ(2)

トンネルを抜けると、そこは雪国だった

そろそろ限界を感じている

それでもね、私はOSのこと、大好きだから。

このときおなか痛くてアイス食べられなかったんですよ、実は(食べたかったんだけど)

大丈夫です、樹海に行こうとしていたわけではありません(ご心配をおかけしました…)

大都会岡山に行ったのです(休職中の高校の後輩を訪ねて)

ミブリムのために精神を落ち着けようね〜

精神が終わっていても低レイヤデバッグエスパー能力は健在だという証明(いいはなし)

推しアーティストWhite Lemonの新曲「天使のヒカリ」、まじでよいのでみんな聴いて(命が、救われています…)

3月

ポケモンウォッシュ!(物理)

https://x.com/hikalium/status/1763865807619305727

お給料、減りました

みがわりを配置しています(いまも私の席で仕事のみがわりをしてくれているはずです)

お気持ち

だめです、仕事はお金の分だけにしような

これ、ずっと考えてる(年末にも同様のことを書いていたことにいま気づいた)

やっぱり左耳壊れてる(年末も壊れてる)

そうですあなたはつかれています。もっと早く気づいて?(気づけてよかった)

TODO(hikalium): これを使うように書き換える

だから休みの日に仕事っぽいことをするのをやめなさーい!

まあQEMUのバグだったんでOK(OKではないが)

足先はもうずっと冷たいです。

4月

セキュキャンの季節が、今年もやってくる…(役職++)

いま休もうか迷っている人は、私が結果的にこの数カ月後に長く休んでいることを思い出してください。その直感はきっと正しい。

事実は小説よりも奇なり(うれしいね!)

流行に乗り遅れました(ずっと乗りたくなかったのに)

カプリコーン、かわいい

うん、そうだね、もっと休むといいよ…

これはね、限界のサインです

5月

そうだぞ

大丈夫?(だめです)

大事なことなのでN回言っておきます

ことしもSTEPの季節がやってきた(これのために生きながらえていた面があると思う)

さっきのQEMUのバグに対するパッチを発射した(merged)

疲弊しています

実はこのあたりでfully-stuckにプロフィールが変わっていました。気づいていましたか?

久々に「七夜の願い星 ジラーチ」を観るなど(ジラーチ、すき)

TCPと仲良くなるなど

たしかにいま思い返すと医者にそう思われてもおかしくない状況だったかもしれない

別にクビになる予定はないが、コンセント増やしたりできるのはまじで便利なのでおすすめです

界隈の人間、多才

洗濯機分解祭り(放熱器に挟まっていたほこりを洗い流したらだいぶ乾燥時間が改善した)(これ構造に根本的な欠陥があると思うの)

ハネッコ on ニャオハ!(いまもこの状態です。かわいいね!)

ストレスへの対処としてポケモンを捕獲

さらに捕獲

職場にコダックが登場(いいはなし)

メカクレ勢、すき

checksumの計算、間違えると詰まりがち

6月

名前というものに対する暗黙のバイアスを認識しましょう(大事なことなのでN回言いました)

まだ選んでないです…

空を、眺めています

Chromeちゃんです(まさか数カ月後に声優がついて歌までリリースされるなんて…)

大本営発表(まあ合理的ではあると思う。うまく行けば、ね。(それは我々の肩にかかっているのよ))

ストレスコーピングの一例(ポケモンのお迎え)

渋谷拠点のジラーチ(他にも新宿拠点のジラーチや、オフィスの机の上のジラーチなどがいます)

新曲。ほんとうにありがとう…

リーフィア、きれいな空気をくれます

音楽に救われています

一般論です(一般論です)

つかれてるね

7月

ストレスMAXのご様子(まじで限界だった)

お気持ち

情報、公開!(私は最初の「未知のバイナリの読み方」のHackを担当しました)

OSを書く仕事の有給を取り、OSを書くなど

超絶有能同僚elkurin氏のプロジェクトが爆発四散した件が公開情報になるなど (でもおかげで(?)天才マダミス作家elkurin先生が誕生したのでいい面もあった(elkurin先生のマダミス面白いので、ぜひ!))

労働への疲弊が極大を迎える

ポケモンはネットワークと命を救っています

ポケモンは命を救っています(まじでミブリムは手に乗せて夜を耐えていたため)

make 夏休み

8月

3Dプリンタと戯れるなど

巨大チョコミントを信じよ

hikalium(概念(物理))

…の実体化

お風呂タイムは労働です(労働ではないがデバッグタイムではある)

クローゼットはケーブルをかける場所、はっきりわかんだね…

リボン自作入門

ポケモンに救われています

セキュキャン、最後のクロスウェーブ府中編

リコリスキャンプ2024

"I'll be back..."

登 壇 者 追 加

サイボウズ・ラボユース合宿にもポケモンを連れて行くなど

電工二種実技、合格!

Binary Hacks Rebooted, 着弾(私の名前が表紙に載っているはじめての商業誌…ありがたや)

休み、延長〜

久々の健康を摂取 as a guest...

Simoun」良いアニメです。みんな観て…ただの百合アニメじゃないから…(個人の感想です)

書泉グランデのイベントに出るなど(貴重な経験に感謝)

9月

やっと私の自作OSがインターネットの海に出れるようになりました。長かった…(しかしこれは終わりではなく始まりである)

M5Stamp Fly, おもしろいです(部屋の中で気軽に飛ばせるし、ソフトウエアを気軽に改造できるのがいい)

Integration test result: PASS

アイスはすべてを救います(要出典)

キョートです

もちろんポケモンを連れ歩いています

そうしてシステムソフトウェアの国際学会 APSys2024を聴講

怒られの発生(時差でつまらないことで怒られてかなしいお気持ちに)

情報科学若手の会に行きました(去年楽しかったので半分旅行気分で行きました)(QRコード破壊サドンデスバトルが楽しかったです)

Linux Kernelのいいはなし

部屋にいるポケモンは増加の一途を辿っている

肉はすべてを救います(お誘い大歓迎です!!!)

OSが悪くなかった例です(USBむずかしいね…)

「先輩はおとこのこ」はいいぞ(よすぎてお腹が壊れた)

10月

この世界のすべての争いを 終わらせる準備ができました

天才マダミス作家elkurinの製品版、好評発売中!

バケッチャというポケモン、よすぎた(陰の者っぽいところが共感できすぎる)

読解 gcode

除湿装置自作入門(今年は秋が夏になっていた)

4台目

ぶーんという騒音を消す方法 検索

hikaliumism

d0iasm氏の本、出来…!

久々のリングフィット起動(なおこれ以降2024年が終わるまで一回もやっていない模様)

11月

コンセント生やし放題カード、着弾(うれしいね)

d0iasm氏の本、着弾(なんとこの本で自作したブラウザは私のWasabiOSの上で動きます!上から下までRustづくし!)

本の影響でレポジトリのクローン数が爆上がりしている様子

メタモン、私の一番の推しポケモンです…

「[作って学ぶ]ブラウザのしくみ」めっちゃ売れてるらしいです(おすすめです!)

プログラマーのジレンマ、痛いほどわかる(おすすめです)

休養、うまくなりましょう!(少しずつうまくなってきた気がする)

3Dプリンタはすべてを救ってくれる(なおモデリングを除く)

ゲームマーケット、同僚氏の様子を観にいったら同僚たちのたまり場と化していてよかった(しかもめっちゃ売れたらしく、いい話!!!)

困ったときはNvVarsを消すとよいです。人間も同じですね!(?)(なおPermission Denied)

洗濯機製造各社、開発者用デバッグポートの公開をお待ちしています…(きびしそう)

ついにHypervisor拡張あり物理RISC-Vマシンが出たらしい。いつ届くかな〜(まだ届いてない)

RustのPinについてのお気持ち(なおPull Requestを投げたところ、メンテナの人いわく、こんな一行変更ではないレベルで訂正が必要なので、それができたら歓迎だよ、というノリらしい(なので一旦Closeされた))

キュワワーの骨を作成(3Dプリンタは一家に一台必要であることの証左)

なんか我々の労働のプロダクトが「CV:上坂すみれ」になったらしく、そんなことあるんだ、とみんなで驚くなどしていた

forkできたら就職したい企業、いっぱいある…

電気工事の免許を行使…

12月

サーバーは飼うものです、と皆様に布教したい(真顔)

隣の国がどったんばったん

休みにすることといえば、pancake time!

高い点数が出ました

d0iasmさんの本が新聞に乗っているらしいの、いい話(すごい)

タイムゾーンの解釈を変更することで、アドベントカレンダーの記事を間に合うように滑り込み生成(アウト)

甘いもの、だいじなので

うちのブイズ

Serial experiments hikalium, スタート(暗い過去の回想録。lainを知っていてこういうコンテンツだとわかっている人は見てね。そうでない人は見ないほうが平穏かもしれない)

プロセカはやってないんですが、暁山瑞希のストーリーは、解像度が高すぎて泣いた。

少しずつだが健康管理がうまくなっている様子

…まだ完璧ではないが

詳細についてはおそらく来年語るが、人生ハードモードhikalium, 新イベント追加です(もうこの種のイベントいらないんだが…)

左耳、故障

12月25日はルーターさん、スイッチさん、サーバーさんに感謝を伝える日です。いいね?

執筆が…終わらない…

年末最後の配信は安定の投機的スパチャです(ありがとうございます!)

「その言語はOSを書けますか?」「…はい!」

EOY

2025年の抱負

「過去からの脱却」に向け邁進します。本年もどうか、よろしくお願いいたします!

今年のWasabiOS進捗振り返り

この記事は「自作OS Advent Calendar 2024」の7日目の記事です(JSTとは言ってない)

自作OS Advent Calendar 2024 - Adventar

WasabiOS とは

私が日本国内で開発している、Rustで書かれたOSなので、Wa (和) + Sabi (Rust) = Wasabi という名前になっています。

ちなみに、この上で動くウェブブラウザを、いつも配信を一緒にやっている d0iasm さんが実装してくれて、 それについてはつい先日、書籍が出版されたので、興味のある方はそちらもぜひご一読ください!

direct.gihyo.jp

そういうわけで、WasabiOSは、雑にWebブラウザが動く程度の機能があるOSだよ、というのが大雑把な説明なのですが、もう少し細かく、今年の開発の進捗を振り返りながらお話しましょう。

1月あたり

年初の書き初めです(これ、今年だったのか…去年くらいかと思ってました…)

PEバイナリと和解した

もう少し解説すると、WasabiOSはUEFIのみに対応するOSなので、OS本体も含めすべてがひとつのPEバイナリとなっています。 そのため、QEMUで自作OSを実行してデバッグする際に一般的に用いられる、gdbからremote target としてQEMUをattachして、QEMUにELFファイルのありかも教えてあげることで、ディスアセンブルされたコードを読んだりする手法は、そのままではうまく動きません。 PEはWindowsワールドのPDBというデバッグ情報形式を使っているのに対して、ELFはLinuxワールドのDWARFというデバッグ情報形式を利用しているのが原因です。

とはいえ、自作OSで例外ハンドラが正しく動作しないようなタイプのバグをデバッグする際には、現在の命令ポインタが、プログラム中のどの機械語、もしくはソースコードの行に対応するのかをどうしても知りたくなります。 これを実現するには、UEFIがロードするPEファイルが任意のアドレスに配置されうるため、PEファイルにかかれているセグメントのアドレス範囲と、実際にロードされたプログラムのアドレス範囲のずれを特定する必要があります。 そのために、OSが実際にロードされたメモリ上の位置をなんとかして知る必要があるわけですが、これは実は簡単にできます。 結論から言えば、雑にどこかの関数のポインタのアドレスを表示してあげればよいのです。

/// This function prints the addr of itself for debug purpose.
#[no_mangle]
pub fn print_kernel_debug_metadata() {
    // Note: This log message is used by the e2etest and dbgutil
    // so please do not edit if you are unsure!
    info!(
        "DEBUG_METADATA: print_kernel_debug_metadata = {:#018p}",
        print_kernel_debug_metadata as *const ()
    );
}

wasabi/os/src/debug.rs at 8e23542da41be26f37d52f2be1b728c06c53fffa · hikalium/wasabi · GitHub

この関数はOS起動時の比較的初期に呼ばれ、シリアルポートに関数の実際のアドレスが出力されます。 make runを実行した際には、QEMUのシリアルポート出力をファイルに保存するよう、QEMUの引数で指定しているので、あとはそこからいい感じにこの数値を切り出してあげれば、 PEファイル中に書かれているアドレスと、実際のメモリ上のアドレスの差が判明します。

あとは、これとシンボルテーブルを突き合わせればいいのですが、gdbは標準でPEバイナリのデバッグ情報を解釈してくれません。 これの解決策は、随分前にretrageさんが書いてくれたブログがあるので、それを参考にするとよいです(ありがとうございます!)

retrage01.hateblo.jp

で、この記事を読んでみると、retrageさん謹製のツールを使って、gdbに食べさせられる形式を生成するという手法になっています。

github.com

とはいえ、今回はせっかく全部Rustで書いているので、なんとかならないかなーと思って、結果自分でdbgutilというスクリプトを書きました。

github.com

PEをパースする部分はpdbという外部crateですが、結構便利なのでおすすめです。

github.com

これで、make symbolsってやると、直近に実行したWasabiOSのバイナリに含まれるシンボルが出力されたり、 make crashって実行すれば、QEMUがトリプルフォルトで落ちたログをパースして、そのアドレスに相当するシンボルの情報を出力してくれるようにしておきました。

とはいえ、ここらへんの機能は、私が必要なときに必要なものを実装して、テストは特に書いていなかったので、毎回動かなくなっていて手直ししています。今度はちゃんとテスト書こう…。

そこから8月くらいまで

さて、ここから8月くらいまでは、怒涛の実装とデバッグ期間になっていました。

主な実装内容はというと、

といった感じでしょうか。

さらに今回は、d0iasmさんのブラウザという、そこそこ大きな、別の開発者による、独立したアプリが存在していたので、noliと命名したライブラリの整備や、描画・UI関連のAPIの実装などもありました。 自作OSで最初から独立した開発者のアプリが存在するケースは珍しいと思いますが、自分の変更によってアプリ開発者からバグ報告が飛んでくると、動作を破壊して申し訳ない気持ちと同時に、うおーOSみたいじゃん!(OSです)という気持ちになれて、非常に面白かったです。

ちなみにライブラリの名前noliは、Wasabiとかの「寿司」っぽいテイストつながりの「海苔」と、OSとアプリをつなぐ存在である、という、くっつけるほうの「のり」をかけ合わせたものになります。

さらにいうと、ブラウザの名前sabaは"SAmple Browser App"ですが、もちろんこれも魚のサバを意識しています。

システムコールについては、実装に時間がかかることが明らかだったので、最初はnoliのレイヤーにモックを仕込んでおいて、それを利用してブラウザ側を開発してもらうようにしていました。 たとえば、TCPのreadをすると、問答無用でexample.comのHTMLソースが返ってくるコードを最初は入れておき、あとでそれを実際の実装に置き換えるなどしていました。

github.com

DNSの問い合わせに関しても、NXDOMAINを返すドメイン名や、127.0.0.1を常に返すドメイン名などを独自に設定して、それに対応する処理を最初はnoliに、次はOS側に、というふうに、実装が進むにつれてモックを削除したり、OS側に移動させるなどして、アプリの開発のしやすさと、OSの実装の並行作業を実現しました。

ちなみに .invalidというTLDは、NXDOMAINを常に返すドメインとして扱いなさい、とRFC6761には書かれています。

The domain "invalid." and any names falling within ".invalid." are special in the ways listed below. Users MAY assume that queries for "invalid" names will always return NXDOMAIN responses. Name resolution APIs and libraries SHOULD recognize "invalid" names as special and SHOULD always return immediate negative responses.

本当は全部の.invalid.をハンドルするべき(SHOULD)ではありますが、いまの実装ではwasabitest.example.invalidだけがsyscallの実装でNXDOMAINを即座に返すようになっています。

(というのも、リモートのDNSサーバーがNXDOMAINを返すようなケースもテストしたいので、すべてをハンドルしてしまうと外向けにNXDOMAINを返してくれることが保証されているクエリを投げられなくなってしまうからです(というのは今考えた言い訳で、まだテストは実装していません…あとでやります…))

あと、ブラウザアプリ実装時にローカルにWebサーバーを立てて実験したいときのために、QEMUのユーザーネットワークでホストマシンに相当する10.0.2.2を必ず返すhost.testというドメインもハードコードされていたりします。

もしかすると、世の中のハードコードされている各種ドメイン名を集めてみると、面白いかもしれませんね!

9月くらい

TCP/IPまわりの実装が動くようになり、d0iasmさんが実装したブラウザも動くようになりました!!!

TCP/IP, 実ははじめて実装したんですが、これ結構楽しいですね!パケットキャプチャとにらめっこしつつ、ちょっとずつ応答できるケースを増やしていくと、いつの間にか動く、というとても楽しい経験なので、みなさんもぜひやってみてください! というか、TCPの再送という仕組みがあるおかげで、うまくいっていないときには向こうからパケットがどんどん再送されてくるので、あー動いていないなあ、と気づけて、デバッグもやりやすかったです。

ちなみに、私はチェックサムの計算で無限回詰まりました。

ここでデバッグに大いに役に立ったのが、tsharkちゃんです。

 tshark -V -o ip.check_checksum:TRUE -o tcp.check_checksum:TRUE -r log/dump_net1.pcap

みたいな感じにしてあげると、チェックサムの検証結果をTCPのパケットごとに出してくれるので、QEMUのパケットダンプオプションと併用して、ぜひデバッグにお役立てください。

ちなみに私はそういったデバッグに役立つコマンド片をMakefileにしれっと紛れ込ませていたりするので、ぜひそちらも参考にしてみるとよいです。 tsharkのJSON形式出力とかは、かなり便利だったので、ぜひ使うと良いと思います。

.PHONY : tshark
tshark:
    @tshark -V -o ip.check_checksum:TRUE -o tcp.check_checksum:TRUE -r log/dump_net1.pcap

.PHONY : tshark_json
tshark_json:
    @tshark -V -o ip.check_checksum:TRUE -x -T json -r log/dump_net1.pcap | jq .

.PHONY : tshark_tcp
tshark_tcp:
    @tshark -V -o ip.check_checksum:TRUE -T json -Y tcp -r log/dump_net1.pcap | jq -c '.[]._source.layers | {src_ip: .ip."ip.src", dst_ip: .ip."ip.dst", src: .tcp."tcp.srcport", dst: .tcp."tcp.dstport", flags: .tcp."tcp.flags_tree"."tcp.flags.str", seq: .tcp."tcp.seq_raw", ack_raw: .tcp."tcp.ack_raw"}'

.PHONY : tcp_hello
tcp_hello:
    echo "hello" | nc localhost ${TCP_FORWARD_PORT}

.PHONY : tcp_echo_server
tcp_echo_server:
    socat -v tcp-l:15000,reuseaddr,fork exec:'/bin/cat'

https://github.com/hikalium/wasabi/blob/8e23542da41be26f37d52f2be1b728c06c53fffa/Makefile#L284-L306

End-to-end test

WasabiではEnd-to-end test (e2etest) にも力を入れていて、e2etestディレクトリ配下にコードが格納されています。

make run_e2e_testと実行すれば、QEMUを画面無しで起動して、ネットワーク周りの機能やキー入力、アプリのロードと実行が正しく動作しているかを、ユーザーと同様の外部入出力レベルでテストすることができます。実装コストや実行時間はかかりますが、これが通れば一通りの機能は動く、という安心感をもって作業を進められるのは、とても心強かったです!

また、async/awaitを使えるRustの強みが、ここでも生かされているなあ、と個人的には感じました。もし興味があれば、e2etest/src/main.rsにテストの実装が、同じディレクトリにQEMUの起動などをラップするコードが配置されていますので、一度読んでみてください。

github.com

で、今はなにやってるの?

休養しつつ、日本語を書く締め切りに追われてます。お楽しみに!!!(無理せず頑張ります!)

2023年の振り返り

例年通り、今年あったことの概要を画像とツイートで一気にまとめておく。

埋め込みが多いので重いですが耐えてください…。(パフォーマンステストに使える疑惑すらある。)

1月

元旦からVRM(gLTF)を読むなど

そこそこ理解した(disassemblium)

なおきわめて雑ではあるが情報源などのリンクはまとめてあるので以下参照

hikalium.hatenablog.jp

人生大体u32、みじかいね…

レゴ、再入門(あれ、今年だったっけ?今や超大量にあるんだが…)

新曲、公開(私は作詞をしました)(作詞・作曲・歌、みんなソフトウェアエンジニアです)

d0iasm氏の担当(このドット絵超好き、グッズほしいな…)

深夜のアイス

mocopi!

そしてmocopiで動くhikalium

余ったRTX2080を入れておく

そしてレイオフの嵐が襲来。実際に同じオフィスで一緒に働いていた方がレイオフされたのを見るのはつらかった…(つらい)

まじでこの週は仕事にならなかった(チームのみんなでシュークリームを食べた)

新しい頭、購入

すたありんく(散財ラッシュ)(なるほど今思えばレイオフは人々にストレスを与えることで散財を促進し経済を活性化させたってこと?(無理のある好意的解釈))

5桁になってしまいました…(あの頃はTwitterだった(「あのころはフリードリヒがいた」のノリで))

うさぎ年だったので(そうだったんだ…忘れてた)

PEバイナリと和解せよ

OS Girls

甘いものは正義(ちなみにこのラテは同僚が淹れてくれたルイボスラテ。とってもおいしい!)

2月

Linuxマシンでもテプラを操作したい!」→プロトコル解析した

ふつうにこれで色々自動化できたので大満足

もちろんテープもいっぱい買いました(散財)

レゴへの傾倒が感じられる(精神安定剤なのでね)

もちろん既存のものを作るだけではなく、創意工夫も凝らしてね(なお現在はさらに改良して場所も移動しましたが未だにちゃんと実用しています)

この世界の平和をね、守っています(ときには力が必要なので)

「[職権修正]女」です(制度と運用はきちんと考えて作って欲しいものですね…(ちなみにこの後無料でマイナンバーカードを再発行できる旨お役所から連絡があり、問題は解決した))

白い機材、すき!

事務処理用社会的個人識別情報データベース更新(変更記録付き)

今月の破壊

3月

精神安定作業(けっこう楽しかった)

机の上の状況(いまも壁にかかっています)

理想と現実、時間と労力、実装と検証

セキュキャンフォーラムのパネルディスカッションに参加。なぜ自作をするのか、人々の意見や体験を聴けてよかった!

頭を強化しようと思って…

CPUが頑張っていると私も頑張ろうって思えるんです

4月

小学生の頃の私が読んでいた本一覧

同じ幼稚園に通っていた知人と大学生になってSTEPで再会した(事実は小説よりも奇なり)のでその人とパンケーキを食べつつ人生を語らうなどした

リアルワールドでは、はじめまして!の季節(そして高校の頃の部活の後輩が同じフロアにやってくるという熱い展開)

そして今年もOS自作ゼミやるよ!という告知(OS自作ゼミ卒業生でベテランチューターの森さんがついに講師に!)

つかれたときはクレープ!

チョコミント

ポッピングシャワー!(私はアイスでできています)

抹茶〜!

逸般の誤家庭レベルがアップした!!

そしてこうなる

わらしべ長者

ストロベリーチーズケーキ!

ポッピングシャワー!(この存在いつ見てもポッピングシャワー食べてるな…)

朝ごはんはストロベリー!

半年でレゴブロックの山が…(これでもごく一部)

ChatGPTちゃんの優しさに救われるなど

なおこの後少なくとも3件のアイス画像が当月中にはありましたが、省略させていただきます(アイスの食べ過ぎ)

6月

レビューを少しだけお手伝いしたnutaさんのマイクロカーネル本が出版された。めでたい!(論文と実装を交えてマイクロカーネルを語っている稀有な書籍なので、おすすめです!)

労働で出席率悪いと成績に響かせるよというお知らせがあって悲しい気持ちになるなど(私達って、信頼されてないんですね…)

7月

積みboard++(年明けに発送されるっぽい)

おひとりさまMisskeyインスタンスはじめました(ちゃんとこの先も運用していきます!)

私財をなげうってChromeOSを良くしようとする存在になってる(ほしかっただけ)

折り紙はいいねぇ…リリンの生み出した文化の極みだよ

デデキント切断(これ以降ドレンのつまりは発生していない、つまりそういうこと)

散財(ポケモンに囲まれた生活)

8月

セキュキャン準備(Tシャツがたくさんたまって助かる)

キャンプごはん!

キャンプ終了後にYちゃんさんと撮った「さかな〜」「チンアナゴ〜」の画像です(たのしかった!)

そしてキャンプ後は初のアメリカ出張!(まずは空港ラウンジで某カレーを摂取)

そしてUSのChromeOSの人々が入ってる建物に到着!(ビジネスクラスで快適な旅だった…)

アメリカのオフィスでも本日の健康を摂取…

ホテルの朝食はおいしいが野菜がない(野菜は貴重だね)

車がないと人権がない図です(車だと徒歩の16倍時間がかかるケース)

コンピューター歴史博物館でhikaliumを出力してきた

Ciscoの由来がサンフラン「シスコ」だったとは…(この橋がロゴのモチーフ)

CとRustで生きていくぞという強い意志(?)

サンフランシスコでガタンゴトン(超急勾配)

たとえアイスがおいしくても500mlを一気に食べてはいけない(教訓)

日本に帰ってきてから、The Missing というゲームの配信動画を始めるなど(エンディング到達済み)

アメリカみやげ、3倍キットカット…(でかい)

カフェインを摂取…

9月

iOSDC NOC オンライン参加(今年はNOC業は主にアドバイザとして助言をする感じで、実働はあまりしませんでした…そして人々が強かったので実質仕事がなかった!すばらしい!)

ADS-Bの受信をはじめてFlightradar24のフル機能を使えるようになるなど

ジラーチかわいい!ハネッコかわいい!(明るい色合いが好き)

虫が出現してつらいつらいになるなど(ごめんね虫さん…)

低レイヤ関連でおすすめの本の一覧を書いた。仕事場で私設図書館もやっていますので、オフィスにいる人でこの本読んでみたいな〜という方がいらっしゃれば私の机まで来てみてください!(本とChromebookが山積みになっている)

hikalium.hatenablog.jp

オシロのある家はいい家

ニンフィアになりたいね…

10月

情報科学若手の会に参加してきた

とりあえずね、三連休の間はね、労働のことはすっかり忘れて、軽井沢へ行くよ(情報科学若手の会)

https://twitter.com/hikalium/status/1710272854787526897

駅から会場までの移動チャレンジに失敗しかけたが、おかげで量子計算の人々と知り合えた!(遅刻)

多少遅刻しつつ @yyu @nkowne63 さんとタクシー同乗させてもらって到着…(本日のタスク、終了!!)

https://x.com/hikalium/status/1710523749051892059

ちなみに私の発表した内容と参加記はすでに別記事にまとめてあるので、こちらもどうぞ!

hikalium.hatenablog.jp

そして高まる第2拠点の機運…(伏線)

居住空間、2拠点目を確保する機運が高まっている…(キャッシュは大事なので(?))

https://twitter.com/hikalium/status/1711290553256788256

急に思い立って、自作OSに使いやすい安価で便利なボード情報を集め始めるなど(ちゃんとメンテしたいね…!)

物理構成の不可逆な変更を強要するのは目的に照らし合わせても合理性がないよね、という判決がやっと出たのはいいニュースだった(人類もちゃんと学んでるんだね…)(まあ私はもう手遅れだけど)

自作OSでの謎のバグをエスパーで解決してしまった自分に自分で震えたので、その裏側を解説してみるなどした(長くやっていればそれだけエスパー力が高まるのでたまに役立つときがあったりなかったりする)

そしてポチったPixel Watch 2... (毎日使うものだからね!)

突然の猿島インターン含めチームの人々と一緒に肉を焼きまくったりお散歩したりした!)

辛いものを食べるとおなかが壊れることがわかった。気をつけよう。(自分の物理アバターの特性を把握するの、ソフトウエアである私の大事なお仕事!)

Google Domains -> Cloudflare の移行でミスって、私と同期の二人が勢いで作ったKUE-CHIP2エミュレータが一時的に落ちていたところ、速攻で大学から連絡が来たので直すなどした(まだ使ってもらえているの、嬉しいね!)

なお他大学の方の役にも立っていた模様(ソフトウエアの力は偉大だね!)

3Dプリンタは現代における家電、はっきりわかるんだね…(なおmikit氏はその後3Dプリンターをお迎えしたとのこと(そして私も同機種をお迎えするなど(?)))

mikit: 3Dプリンタがおうちに欲しいけど、そんなものあったら絶対お婿にいけないので我慢してる

hikalium: すでにおいてある相手ならば無問題かもしれない(部屋に並ぶ2つの3Dプリンタを横目に…)

mikit: もしかしていまぼくプロポーズされてますか?///

hikalium: それはね、若干頭をよぎったよね///

call_cc: (まずはmikitさんは3Dプリンタを2台買わないと、だよなぁ。)

mikit: たしかに、3Dプリンタ2台というのは今どきの婿入り道具として珍しくもないですからね(?)

https://twitter.com/m1kit/status/1713800308630163695

第2拠点の鍵、ゲット(はやい)

とにかくこの月は全般的に労働に対して疲れているというイメージ。まあ寒いし仕方なかったよね。よく乗り切った!

11月

チョコミントだけが私を救ってくれる…

「好きなことをする努力家はね、最強なんですよ」

チョコミント以外も私を救ってくれる…

やはりポッピングシャワーこそ至高(アイスしか食べてないなこの存在)

散財(家電なのでこれは必要経費!)

おくすり

みんなホワイトレモンの曲を聴け(シンガーソングライターかつソフトウエアエンジニアであるホワイトレモン氏の曲は最高)(はじめてペンライトを振りました)

またアイス

出勤時刻は何時ですか?(ガタガタ…)

石をつくる会ことISHI会の人々の発言(レイヤが低くてすき)

第2拠点に10Gの回線がやってきた!!!

「好きなことを続けること それは楽しいだけじゃない」(「群青」YOASOBI より)(人前で歌う実績を解除)

技術書典!(新刊なし…来年こそは…!!!!!!!!)

ちゃんと在庫全部捌けたのでよかった(みなさんありがとうございました!)

リンク速度1Gのルーターは遅すぎるのでだめ(時代は10Gとか100G!)

そして新しい3Dプリンターに感動するなど(まじですごい、これ以降ほぼ毎週、時には毎日なにかしら印刷していた)

Benchyもこの通り(はやい)

「この世で造花より綺麗な花はないわ なぜならば総ては嘘でできてる Antipathy world...」

(「フォニイ」 ツミキ feat. 可不 より)

何をしても超改善が期待できる、ってコトぉ????(超改善、していきたいですね…)

STEP10周年記念の会があって、人々と再会した。私の講義で低レイヤの沼にはまってくれた人々が複数人声をかけてくれたの、本当に嬉しかった…(そしてなぜか片手で机を使わず空中で鶴が折れるようになった。義体制御技術の進化…)

一方で同時に結構絶望している。まだ間に合うかな…。

ということでかわいいものに囲われて精神を回復!!!!!

そして色違いイーブイをお迎え!(ポケモンスリープ、まじで睡眠に対してプラスの結果が得られているので、感謝しかない…)

ごはん

からの配信

BSSセクションで助かっていた人々がたくさんいたのでよかった(よかった!)

未踏ジュニアでマイクロカーネルを作ってスーパークリエイター認定されたhorizonさんと再会。数年前にオープンソースカンファレンスで私と話したのがきっかけで自作OSに傾倒することになったとのこと。熱い!!!(そして運良く弊社でランチを囲む機会に恵まれたのでとても嬉しかった。)

プロジェクトの詳細はこちらから見れるので、興味のある方はぜひ!

jr.mitou.org

その後疲れて倒れていたので巨大サーティーワンに救ってもらいました

もちろんポッピングシャワーもあるぞ!

12月

最近まじでChromebookで生活している(もっと良くしていこうな!)

組み立ては 二人以上で やりましょう(ここでfork()する)

担当していたインターンの方が無事に期間を終えた。めっちゃいっぱいコード書いてもらった。ありがとう!

会社の下でテラスタルしてるポケモンがいた

まさかその日にスイッチ(ネットワーク機器でも電子部品でもないほう)2台目でポケモンを新たにプレイし始めることになるとは(たのしいね!)

ディスク容量不足で新宿拠点のマシンが爆発しかけるなど(根本的解決をはやくしなければ…)

12月 ケーキがおいしい 季節です

モンメンエルフーン!かわいいね!

今年最後の会社朝ごはんとサーティーワン

Googleって会社はすごい!私達も見習わないとね!(真顔)(できることを、ちゃんとやろうな)

総評

  • よくこの1年生き延びた。ストレスの多い一年だった。経済とそれに振り回される社会に翻弄されていた。おつかれさまでした。
  • アイスはすべてを解決してくれる。アイスを食べましょう。(食べ過ぎに注意)
  • 今年は過去の自分の努力に救われてなんとか生きながらえていた。貯金を切り崩している年だったかもしれない。(でも無理をしなかったのはえらかった。倒れたら負けなのでね。戦い続けましょう。)
  • 仕事でも日常でも、物事を始めるだけでなく、完了させることができるようになろう。人生は、まだ完了しなくていいけどね!(ここ重要)
  • 来年は、今と未来に良い影響を与えられるような具体的な結果を出そう。すでに着手はしているので、それを完了させようね。(お楽しみに。)

本年も皆様大変お世話になりました。ありがとうございました!

来年も無理せず着実に這って進んでいこうと思いますので、どうかよろしくお願いいたします。

よいお年をお迎えください! Happy new year!

情報科学若手の会で登壇してきた

先週末の三連休、第56回情報科学若手の会に、若手特別講演の枠でありがたくも呼んでいただけたので、参加して発表してきました!

発表で使用したスライドはこちらにおいておきます:

ちなみに開催期間中に人々と雑談をしていたら知ってびっくりしたのですが、なんとこの会は1968年7月から続く由緒正しきイベントで、インターネットの父として知られる村井純先生が1955年生まれであることを考えると、驚くほど歴史が長いです。

過去の開催報告も、1996年以降の分については公式ページに存在しており、参加者の名前を見ると、私が大学生の頃に退職された筧捷彦先生が幹事として参加されていたりと、なかなか衝撃的に歴史の長いイベントに気軽に足を踏み入れてしまったことに開催中に気づいてしまいました。沼ですね…。(いいぞ!)

時系列ざっくりまとめ

準備

ちょうど休みがほしい感じのタイミングだったので、労働からdetachして過ごす日々に、わくわくしながら準備をしていました。

なお準備ができているとは言っていない(スライドも荷物も、前日の段階では進捗0でした…急いでがんばりました)。

ちなみにドライヤーがないとしおりに記載されていたので、家からつよいドライヤーを持参しました。 (実際にはふつうの強さのドライヤーはデフォルトで用意されていたので、なくても大丈夫そうでした。来年以降の参考までに。)

初日

新幹線っていう高速回線でhikaliumは転送されました。

思ったより寒く、猛暑の夏から冬の始まりに急に飛ばされたような感じでした。

そして、問題はここから研修施設までの移動をどうするか。まあなんとかなるでしょ、の精神で来つつ、どうやら知り合いが車でくるらしいという情報を聞き、便乗させてもらおうと思っていたところ、なんと渋滞で大幅に遅れそうになっていたとのこと。そうか秋の三連休ですものね、皆様も旅をしたいと思っていらっしゃるのでしょう。仕方ありませんわ…(三連休開催のつらいところ)。

なので、同じく交通手段獲得RTAを走っていた参加者のお二人とエンカウントして、タクシーで現地へと向かうことにしました。

(ちょうどここで量子計算機関連の雑談ができて楽しかったです。)

若干遅刻しつつ参加したところ、「ブログを書くまでが #wakate2023」という、どこかで聞いたようなフレーズが耳に入ってきました。ということで、このブログを書いているわけです。(振り返り、大事なのでね!)

初日の講演は

  • GoでORMを自作する
  • スポンサーセッション : PFNを支えるストレージシステム
  • 招待講演 : 実用水準のプログラミング言語を個人規模でつくる

の三本立てとなっていました。

特に3つ目の、SATySFiの作者gfnさんによる講演は、首を縦に無限回振りたくなるほど、何かを自作する際に発生しがちな感情や思考の機微を的確に説明する講演で非常にためになりました。

ちなみにgfnさんとは帰りのタクシーで一緒になったため、フォントやPDF周りの闇についてわいわい盛り上がることができました。Type 1 fonts は闇が深い、覚えた!

夜は、人々で親睦を深めるために Estimathonという数字予想ゲームが開催され、非常に盛り上がりました。 まず、ルールを理解するところからして「結果が小さくなるほうが得点が高い」という部分が、普段の思考のバイアス(大きい方が得点が高い)に引っ張られて頭がぐるぐるしましたし、その後も、どの程度のレンジで値を特定すればいいのかの戦略など、単なるフェルミ推定的な予想を超えた面白さが満載で、めっちゃ楽しかったです。これ、会社の人々とかと、またやりたいなあと思いました。

なお、我々のチームは頭のネジが外れていたので、途中で結果が爆発的に大きくなってしまい、スコアボードに数値ではなく「とても大きい」と書かれるなどの実績を解除してしまいました…(でも楽しかった!!)

そのあとは、各自が持ち寄った大量のお菓子を囲みながら、人々とわいわい喋りつつ、私はスライド作成RTAをしていました…。

あと、次の日のLT大会に向けて準備していたmomokaさんの検証を手伝っていたら、なんか平日に見慣れた画面に突入してしまい労働の波動を感じるなどしてしまいました(オープンソースだからへーきへーき!!これは仕事じゃないもんね!!)

二日目

早起きして朝食を食べる超健康生活(なお午前3時までスライド作成やパケットキャプチャをしていた模様)をはじめつつ、本題の講演二日目が始まりました。

「Wasmを実行するunikernelとWasmコンパイラの開発」という発表でsaza_kuさんとainnoさんが提案していたエコシステムは、確かに上手く行けばめっちゃ熱い話だな…というか我々なんとかOSもこういう方向に進むべきなのでは!?!?という示唆を得られる、とてもわかりやすくためになる発表でした。これからの成果を楽しみにしています!

量子計算機関連の講演も複数あり、まさに冒頭の軽井沢駅から研修所までの道のりを共にしたお二人がそれぞれ発表した内容は、どちらも量子計算の基礎をわかりやすく説明してくれていて、とてもためになりました。

あと、量子コンピューターの世界におけるOSはどのようなものになるのか、ということについてもお二人とお話できて、将来考えてみる価値のある面白いテーマかもしれないな、と感じました。

また、海外では量子コンピューターを自作した人々がいる、という話もあったりと、自作欲をかきたてられるお話も色々と聞くことができました。私も元々量子計算機には少し興味があって、ある程度の知識は本から摂取していたので、それに加えて実際に専門として取り組まれている人々の話を聞くことができたのは、非常に楽しかったです。

そのあとは、私の発表ターンに続き、様々な面白いトピックでの発表が続きました。研究発表的な内容も、ざっくりと概観するような内容も、どちらも摂取できるのが、この会の良いところだなあ、と感じました。

また、そのあとのLTでも、面白い話がたくさんあったのですが、まだ未公開な話も多くあったので、とにかく楽しかったということだけを記しておくことにします。興味がある皆さんは、ぜひ次回の情報科学若手の会に参加を検討されてみてはいかがでしょうか!!!!!!!

そのほかのメモ

参加者のひとり、asu_paraさんの参加記事

hikaliumさんはセキュキャンなどを通してOSとか低レイヤーの人ということで知っていますし、講演もその通りに沿ったものです。ぼく個人としては、自身は概念のようなものと宣言されているにも関わらず、人間的な一面が見られるpostが多いのが趣深い人やなといつも思って、見てます。

と書かれていて、分かるな〜、となっています(まるで他人事のように2)

あとは、低レイヤと高レイヤの話についての視聴者の反応(めっちゃわかる)

他にも、同室の人々が私も含め、みんな引きこもりコンピューター大好きオタクムーブを発動していて面白かったです。(この界隈、性別の偏りが激しいので、相部屋(ひとり)になりがちの人々が久々に修学旅行的な雰囲気の部屋に放り込まれて、結果みんなおふとんの上で座り込んでパソコンカタカタする夜、みたいになっていて最高でした。)momokaさんのあやしいパケット検証を手伝ったり、ainnoさんのRustコードをレビューする会をしたり、たまにはこういうのも楽しいなあ!と感じました。

ちなみに:フィードバックに書き忘れたのですが、女子部屋が最も交通量の多い場所に配置されていたので、もしかすると交通量が少ない廊下の奥の方の部屋のほうがよいのではないか、という話が挙がっていました。来年以降の運営の参考になれば…。

あとは、狭い界隈の人々と物理世界で会えたのがよかったですね。大学の研究室の後輩から「先生が、たまには研究室に顔を出せって言ってましたよ!」と言われたりとか(なおその方はNVDIMMをやろうとしていたところ、Optaneがおしまいになったのでおじゃんになったらしい、かなしいね)、私が行こうと思っていた大学院の研究室に行く予定の学生の方とか(品川先生〜)、セキュキャンつながりの人々とか、とにかく、たくさんの方々と話すことができて、とても楽しかったです。

まとめ

久々のイベント登壇でしたが、面白いことをしているたくさんの方々と会って話せるお泊りイベントはとても楽しかったです。たまには日常を忘れて、こういうイベントに参加するのも、よいアイディアを得たり、今後の道筋を立てる助けになるような気がしました。また来年も参加したいなあ、という気持ちにさせてくれる、快適なイベントを運営してくださった、幹事の皆さまと、参加者のみなさま、ありがとうございました!またどこかでお会いしましょう〜!!

CPUを自作したりコンピューターアーキテクチャを理解するためにおすすめの本の一覧

hikaliumの独断と偏見で、積読は除いている。最近も結構新しい本が色々出ているので、それもいいかもしれないが、ある程度評価の定まった本を探したい場合に参考になれば。

ちなみに、hikaliumがセキュキャンでCPU自作を教えていたときのコードはここにある。参考にならないかもしれないが、おまけにどうぞ。

github.com

ディジタル回路設計とコンピュータアーキテクチャ

ハードウエア記述言語で実際にCPUをつくりながら、各アーキテクチャについても学べる良書。

MIPS版が広く知られているが、ARM版、RISC-V版も登場している。無印版はよくある技術書サイズだが、ARMとRISC-V版は大型本なので、そこらへんの好みとかも勘案するとよいかもしれない。

CPUの創り方

表紙がメイドさんだが、侮ることなかれ。(と私は中学生の時にクラスの人々の前で言いました。)

電子工作初心者の人々にもわかりやすく、回路素子の基本的な扱い方や、データシートの読み方も教えてくれる、実装寄りの良書。

74HCシリーズのICが手に入りにくいという現代的な事情はあるが、等価な回路を上の本などを参考にしつつVerilog-HDLで記述したりすれば、FPGAボードひとつでも等価なものを十分につくることができる。

RISC-V原典 オープンアーキテクチャのススメ

きれいなアーキテクチャで知られるRISC-Vの設計思想の背景や、どうしてその設計が選択されたのか、という思考の背後を知ることができる本。もう少し高いレベルからCPUのことを知りたかったり、RISC-Vについて知りたい場合には役立つ。

コンピュータアーキテクチャ 定量的アプローチ

通称ヘネパタ。大学でこの本を参考にキャッシュの特性を調べるグラフを描画する課題が出て、面白いなあと思った記憶がある。

めちゃくちゃ教科書的な内容なので、読むのは大変だし最初から最後まで読むタイプの本ではないが、持っておいて損はない。ペラペラめくったり目次をみて、興味のあるところや、参考になりそうなところを読む、辞書的な使い方をするのがよい。

作りながら学ぶコンピュータアーキテクチャ

上の方の本の翻訳にも携わっておられる、天野先生の著書。他の本では忘れられがちな、割り込み処理の実装方法が記述されているとネットで聞いて自分も購入した。

絶版本なので手に入れるのは少し大変。図書館などにあるかどうか、確認してみるのも手かもしれない。

上の分厚い辞書みたいな本たちにくらべたら、薄くて読みやすそうな見た目をしているので、一見の価値はある。

FPGAの原理と構成

いま気づいたがこれも天野先生の著書じゃん。おそらくCPU自作をはじめる最も手軽な方法はFPGAを用いることだが、じゃあFPGAって一体どうやって動いているの?というところの疑問を解決してくれる本。これも小さめ技術書によくあるサイズ感なので、電車の中でも読みやすいのでおすすめ。

Chromebookの開発者モードへの遷移をちょっと追う

某ブログに触発されて、公開情報からChromiumOSの気になるところを追う軽い記事でも書こうかなという気持ちに急になったので書いていく。正確さも何も保証しないし、公開情報しか載っていないのであしからず。公開情報であることを示すために出典へのリンクをいっぱい張ります。

Chromebookには開発者モードというunsecureなモードがあって、これを使うと自作OSをブートさせたり色々できて楽しい。それに入るためには、リカバリモードに入ってCtrl-Dを押してEnterを押すという作業が通常必要なわけだが、これで何が起きるのかちょっと追いかけてみよう。

まず、ChromebookBIOSでもUEFIでもないファームウエアを採用している。(一部例外はある。)これはverified bootとかvbootとか呼ばれていて、このあたりにいい感じの図がある。情報が最新の状態に更新されているかは謎だが…(斜め上に視線を動かす)

あと、ここのページにファームウエアまわりの情報が載っていて、こんなかっこいい一文が書いてある。

The Chrome OS firmware is always verified as signed by Google

いいはなし。Root of Trustってやつだね。

さて、このページの下の方に、H2C (UEFI-based), U-boot, Coreboot, とChromebookで使われていた歴代ファームウエアの名前が並び、それに続いてdepthchargeってやつが新し目のChromebookだと使われているぞ!と書いてある。へえ〜!

そこからこんなスライドへもリンクが飛んでいるが、なんと2014年である。まあつまるところ、多分近頃のChromebookはみんなdepthchargeを使ってるんじゃないかな?(適当)

さて、そんなわけで、depthchargeのコードはここにあるわけだが、まずREADMEがない。なるほど…?

ちなみに"depthcharge chromiumos"でググる無機質なGitilesというシステムのページに飛ばされるが、ここは検索機能とかがないので、便利なChromium Code Searchを使おう!超便利だよ!(いつもお世話になっているので頭が上がらない)

さて本題に戻って、とりあえずdepthcharge配下で"recovery"とかの単語で調べると、src/platform/depthcharge/src/vboot/ui.hというファイルがヒットする。

/* Screens. */
enum ui_screen {
    /* Wait screen for EC sync and AUXFW sync */
    UI_SCREEN_FIRMWARE_SYNC             = 0x100,
    /* Broken screen */
    UI_SCREEN_RECOVERY_BROKEN           = 0x110,
    /* Advanced options */
    UI_SCREEN_ADVANCED_OPTIONS          = 0x120,
    /* Language selection screen */
    UI_SCREEN_LANGUAGE_SELECT           = 0x130,
    /* Debug info */
    UI_SCREEN_DEBUG_INFO                = 0x140,
    /* Firmware log */
    UI_SCREEN_FIRMWARE_LOG              = 0x150,
    /* First recovery screen to select recovering from disk or phone */
    UI_SCREEN_RECOVERY_SELECT           = 0x200,
    /* Invalid recovery media inserted */
    UI_SCREEN_RECOVERY_INVALID          = 0x201,
    /* Confirm transition to developer mode */
    UI_SCREEN_RECOVERY_TO_DEV           = 0x202,
    /* Recovery using phone */
    UI_SCREEN_RECOVERY_PHONE_STEP1          = 0x210,
    UI_SCREEN_RECOVERY_PHONE_STEP2          = 0x211,
    /* Recovery using disk */
    UI_SCREEN_RECOVERY_DISK_STEP1           = 0x220,
    UI_SCREEN_RECOVERY_DISK_STEP2           = 0x221,
    UI_SCREEN_RECOVERY_DISK_STEP3           = 0x222,
    /* Developer mode screen */
    UI_SCREEN_DEVELOPER_MODE            = 0x300,

https://source.chromium.org/chromiumos/chromiumos/codesearch/+/main:src/platform/depthcharge/src/vboot/ui.h;l=198-226;drc=3eda3d7bd3195f6d8ff59954e6b2967e42201bcb

UI_SCREEN_RECOVERY_TO_DEVとか、いかにもそれっぽい名前なので、今度はそれで検索すると、src/platform/depthcharge/src/vboot/ui.cというファイルが見つかる。まさに特定のキーを押したときに画面遷移するコードが書いてある!

 /* Manual recovery keyboard shortcuts */
    if (ui->key == UI_KEY_REC_TO_DEV ||
        (CONFIG(DETACHABLE) &&
         ui->key == UI_BUTTON_VOL_UP_DOWN_COMBO_PRESS)) {
        return ui_screen_change(ui, UI_SCREEN_RECOVERY_TO_DEV);
    }

https://source.chromium.org/chromiumos/chromiumos/codesearch/+/main:src/platform/depthcharge/src/vboot/ui.c;l=39-44;drc=fcfa6efb8f221877e771a7bc382267a3ebcdee48

ちなみにここに書いてあるとおり、キーボードが外れるタイプのデバイス(DETACHABLE)だと、音量キーの同時押しでdevmodeに入れることがわかる。(もちろんドキュメントに書いてあるよ!)

さて、じゃあこの画面の遷移した先を見に行こう。

src/platform/depthcharge/src/vboot/ui/screens.c

static vb2_error_t recovery_to_dev_init(struct ui_context *ui)
{
    if (ui->ctx->flags & VB2_CONTEXT_DEVELOPER_MODE) {
        /* Notify the user that they are already in dev mode */
        UI_WARN("Already in dev mode\n");
        return set_ui_error_and_go_back(
            ui, UI_ERROR_DEV_MODE_ALREADY_ENABLED);
    }

    if (!CONFIG(PHYSICAL_PRESENCE_KEYBOARD) &&
        ui_is_physical_presence_pressed()) {
        UI_INFO("Physical presence button stuck?\n");
        return ui_screen_back(ui);
    }


    if (CONFIG(PHYSICAL_PRESENCE_KEYBOARD)) {
        ui->state->selected_item = RECOVERY_TO_DEV_ITEM_CONFIRM;
    } else {
        /*
         * Disable "Confirm" button for other physical presence types.
         */
        UI_SET_BIT(ui->state->hidden_item_mask,
               RECOVERY_TO_DEV_ITEM_CONFIRM);
        ui->state->selected_item = RECOVERY_TO_DEV_ITEM_CANCEL;
    }

    ui->physical_presence_button_pressed = 0;

    return VB2_SUCCESS;
}

static vb2_error_t recovery_to_dev_finalize(struct ui_context *ui)
{
    UI_INFO("Physical presence confirmed!\n");

    /* Validity check, should never happen. */
    if (ui->state->screen->id != UI_SCREEN_RECOVERY_TO_DEV ||
        (ui->ctx->flags & VB2_CONTEXT_DEVELOPER_MODE) ||
        ui->ctx->boot_mode != VB2_BOOT_MODE_MANUAL_RECOVERY) {
        UI_ERROR("ERROR: Dev transition validity check failed\n");
        return VB2_SUCCESS;
    }

    UI_INFO("Enabling dev mode and rebooting...\n");

    if (vb2api_enable_developer_mode(ui->ctx) != VB2_SUCCESS) {
        UI_WARN("Failed to enable developer mode\n");
        return VB2_SUCCESS;
    }

    return VB2_REQUEST_REBOOT_EC_TO_RO;
}

https://source.chromium.org/chromiumos/chromiumos/codesearch/+/main:src/platform/depthcharge/src/vboot/ui/screens.c;l=799-851;drc=85f76f13f623d3e4e3a01224a381abf755f1c1bb

vb2api_enable_developer_mode()ってやつがあやしいね!そして、それを呼んだあとは再起動してるみたい。depthchargeの中にはtest用のmockしかなかったので、ひとつ上のsrc/platformで検索をかけると、vboot_referenceで実装が見つかった。

vb2_error_t vb2api_enable_developer_mode(struct vb2_context *ctx)
{
    if (ctx->boot_mode != VB2_BOOT_MODE_MANUAL_RECOVERY) {
        VB2_DEBUG("ERROR: Can only enable developer mode from manual "
              "recovery mode\n");
        return VB2_ERROR_API_ENABLE_DEV_NOT_ALLOWED;
    }

    uint32_t flags;

    VB2_DEBUG("Enabling developer mode...\n");

    flags = vb2_secdata_firmware_get(ctx, VB2_SECDATA_FIRMWARE_FLAGS);
    flags |= VB2_SECDATA_FIRMWARE_FLAG_DEV_MODE;
    vb2_secdata_firmware_set(ctx, VB2_SECDATA_FIRMWARE_FLAGS, flags);

    VB2_DEBUG("Mode change will take effect on next reboot\n");

    return VB2_SUCCESS;
}

https://source.chromium.org/chromiumos/chromiumos/codesearch/+/main:src/platform/vboot_reference/firmware/2lib/2misc.c;l=433-452;drc=def2f5af7ac03eac3a4a79795275f1cf6cd7f827

VB2_SECDATA_FIRMWARE_FLAG_DEV_MODE ってフラグがセットされるんだね〜

ということでこいつを調べると、フラグの定義も見つかった。

/* Flags for firmware space */
enum vb2_secdata_firmware_flags {
    /*
     * Last boot was developer mode.  TPM ownership is cleared when
     * transitioning to/from developer mode.  Set/cleared by
     * vb2_check_dev_switch().
     */
    VB2_SECDATA_FIRMWARE_FLAG_LAST_BOOT_DEVELOPER = (1 << 0),

    /*
     * Virtual developer mode switch is on.  Set/cleared by the
     * keyboard-controlled dev screens in recovery mode.  Cleared by
     * vb2_check_dev_switch().
     */
    VB2_SECDATA_FIRMWARE_FLAG_DEV_MODE = (1 << 1),
};

https://source.chromium.org/chromiumos/chromiumos/codesearch/+/main:src/platform/vboot_reference/firmware/2lib/include/2secdata.h;l=25-40;drc=6a33a0fca3e4f5cd9c3b3fd4ac0b5b8c7ffc018e

コメントいわく、最後の起動時の状態も記録しておいて、もしnormalからdevに遷移していたら、TPMのownershipをクリアするって書いてあるね!まあ要するに、normalのときに保存されたユーザーの秘密情報がもれないように、なんでもありのdevmodeに遷移するときはデータを消し飛ばすってことなんでしょう。(実際devmodeに移行するとなんかの消去が走っている画面になる。)

次回はその消去の画面のところを見に行きましょう!(オープンソース部分であることを祈りましょう…)

おやすみなさい!