`を使っています(`Volatile`型は[ジェネリック][generic]であり(ほぼ)すべての型をラップできます)。これにより、間違って「普通の」書き込みをこれに対して行わないようにできます。これからは、代わりに`write`メソッドを使わなければいけません。
[generic]: https://doc.rust-lang.org/book/ch10-01-syntax.html
つまり、`Writer::write_byte`メソッドを更新しなければいけません:
```rust
// in src/vga_buffer.rs
impl Writer {
pub fn write_byte(&mut self, byte: u8) {
match byte {
b'\n' => self.new_line(),
byte => {
...
self.buffer.chars[row][col].write(ScreenChar {
ascii_character: byte,
color_code,
});
...
}
}
}
...
}
```
`=`を使った通常の代入の代わりに`write`メソッドを使っています。これにより、コンパイラがこの書き込みを最適化して取り除いてしまわないことが保証されます。
### フォーマットマクロ
Rustのフォーマットマクロもサポートすると良さそうです。そうすると、整数や浮動小数点数といった様々な型を簡単に出力できます。それらをサポートするためには、[`core::fmt::Write`]トレイトを実装する必要があります。このトレイトに必要なメソッドは`write_str`だけです。これは私達の`write_string`によく似ており、戻り値の型が`fmt::Result`であるだけです:
[`core::fmt::Write`]: https://doc.rust-lang.org/nightly/core/fmt/trait.Write.html
```rust
// in src/vga_buffer.rs
use core::fmt;
impl fmt::Write for Writer {
fn write_str(&mut self, s: &str) -> fmt::Result {
self.write_string(s);
Ok(())
}
}
```
`Ok(())`は、`()`型を持つ`Ok`、というだけです。
Rustの組み込みの`write!`/`writeln!`フォーマットマクロが使えるようになりました。
```rust
// in src/vga_buffer.rs
pub fn print_something() {
use core::fmt::Write;
let mut writer = Writer {
column_position: 0,
color_code: ColorCode::new(Color::Yellow, Color::Black),
buffer: unsafe { &mut *(0xb8000 as *mut Buffer) },
};
writer.write_byte(b'H');
writer.write_string("ello! ");
write!(writer, "The numbers are {} and {}", 42, 1.0/3.0).unwrap();
}
```
このようにすると、画面の下端に`Hello! The numbers are 42 and 0.3333333333333333`が見えるはずです。`write!`の呼び出しは`Result`を返し、これは放置されると警告を出すので、[`unwrap`]関数(エラーの際パニックします)をこれに呼び出しています。VGAバッファへの書き込みは絶対に失敗しないので、この場合これは問題ではありません。
[`unwrap`]: https://doc.rust-lang.org/core/result/enum.Result.html#method.unwrap
### 改行
現在、改行や、行に収まらない文字は無視しています。その代わりに、すべての文字を一行上に持っていき(一番上の行は消去されます)、前の行の最初から始めるようにしたいです。これをするために、`Writer`の`new_line`というメソッドの実装を追加します。
```rust
// in src/vga_buffer.rs
impl Writer {
fn new_line(&mut self) {
for row in 1..BUFFER_HEIGHT {
for col in 0..BUFFER_WIDTH {
let character = self.buffer.chars[row][col].read();
self.buffer.chars[row - 1][col].write(character);
}
}
self.clear_row(BUFFER_HEIGHT - 1);
self.column_position = 0;
}
fn clear_row(&mut self, row: usize) {/* TODO */}
}
```
すべての画面の文字をイテレートし、それぞれの文字を一行上に動かします。範囲記法 (`..`) は上端を含まないことに注意してください。また、0行目はシフトしたら画面から除かれるので、この行についても省いています(最初の範囲は`1`から始まっています)。
newlineのプログラムを完成させるには、`clear_row`メソッドを追加すればよいです:
```rust
// in src/vga_buffer.rs
impl Writer {
fn clear_row(&mut self, row: usize) {
let blank = ScreenChar {
ascii_character: b' ',
color_code: self.color_code,
};
for col in 0..BUFFER_WIDTH {
self.buffer.chars[row][col].write(blank);
}
}
}
```
このメソッドはすべての文字を空白文字で書き換えることによって行をクリアしてくれます。
## 大域的なインターフェース
`Writer`のインスタンスを動かさずとも他のモジュールからインターフェースとして使える、大域的なwriterを提供するために、静的な`WRITER`を作りましょう:
```rust
// in src/vga_buffer.rs
pub static WRITER: Writer = Writer {
column_position: 0,
color_code: ColorCode::new(Color::Yellow, Color::Black),
buffer: unsafe { &mut *(0xb8000 as *mut Buffer) },
};
```
しかし、これをコンパイルしようとすると、次のエラーが起こります:
```
error[E0015]: calls in statics are limited to constant functions, tuple structs and tuple variants
(エラー[E0015]: static内における呼び出しは、定数関数、タプル構造体、タプルヴァリアントに限定されています)
--> src/vga_buffer.rs:7:17
|
7 | color_code: ColorCode::new(Color::Yellow, Color::Black),
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error[E0396]: raw pointers cannot be dereferenced in statics
(エラー[E0396]: 生ポインタはstatic内では参照外しできません)
--> src/vga_buffer.rs:8:22
|
8 | buffer: unsafe { &mut *(0xb8000 as *mut Buffer) },
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ dereference of raw pointer in constant
| (定数内での生ポインタの参照外し)
error[E0017]: references in statics may only refer to immutable values
(エラー[E0017]: static内における参照が参照してよいのは不変変数だけです)
--> src/vga_buffer.rs:8:22
|
8 | buffer: unsafe { &mut *(0xb8000 as *mut Buffer) },
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ statics require immutable values
| (staticは不変変数を必要とします)
error[E0017]: references in statics may only refer to immutable values
(エラー[E0017]: static内における参照が参照してよいのは不変変数だけです)
--> src/vga_buffer.rs:8:13
|
8 | buffer: unsafe { &mut *(0xb8000 as *mut Buffer) },
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ statics require immutable values
| (staticは不変変数を必要とします)
```
何が起こっているかを理解するには、実行時に初期化される通常の変数とは対照的に、静的変数はコンパイル時に初期化されるということを知らないといけません。この初期化表現を評価するRustコンパイラのコンポーネントを"[const evaluator]"といいます。この機能はまだ限定的ですが、「[定数内でpanicできるようにする][Allow panicking in constants]」RFCのように、この機能を拡張する作業が現在も進行しています。
[const evaluator]: https://rustc-dev-guide.rust-lang.org/const-eval.html
[Allow panicking in constants]: https://github.com/rust-lang/rfcs/pull/2345
`ColorCode::new`に関する問題は[`const`関数][`const` functions]を使って解決できるかもしれませんが、ここでの根本的な問題は、Rustのconst evaluatorがコンパイル時に生ポインタを参照へと変えることができないということです。いつかうまく行くようになるのかもしれませんが、その時までは、別の方法を行わなければなりません。
[`const` functions]: https://doc.rust-lang.org/reference/const_eval.html#const-functions
### 怠けた静的変数
定数でない関数で一度だけ静的変数を初期化したい、というのはRustにおいてよくある問題です。嬉しいことに、[lazy_static]というクレートにすでに良い解決方法が存在します。このクレートは、初期化が後回しにされる`static`を定義する`lazy_static!`マクロを提供します。その値をコンパイル時に計算する代わりに、この`static`は最初にアクセスされたときに初めて初期化します。したがって、初期化は実行時に起こるので、どんなに複雑な初期化プログラムも可能ということです。
**訳注:** lazyは、普通「遅延(評価)」などと訳されます。「怠けているので、アクセスされるギリギリまで評価されない」という英語のイメージを伝えたかったので上のように訳してみました。
[lazy_static]: https://docs.rs/lazy_static/1.0.1/lazy_static/
私達のプロジェクトに`lazy_static`クレートを追加しましょう:
```toml
# in Cargo.toml
[dependencies.lazy_static]
version = "1.0"
features = ["spin_no_std"]
```
標準ライブラリをリンクしないので、`spin_no_std`機能が必要です。
`lazy_static`を使えば、静的な`WRITER`が問題なく定義できます:
```rust
// in src/vga_buffer.rs
use lazy_static::lazy_static;
lazy_static! {
pub static ref WRITER: Writer = Writer {
column_position: 0,
color_code: ColorCode::new(Color::Yellow, Color::Black),
buffer: unsafe { &mut *(0xb8000 as *mut Buffer) },
};
}
```
しかし、この`WRITER`は不変なので、全く使い物になりません。なぜならこれは、この`WRITER`に何も書き込めないということを意味するからです(私達のすべての書き込みメソッドは`&mut self`を取るからです)。ひとつの解決策には、[可変で静的な変数][mutable static]を使うということがあります。しかし、そうすると、あらゆる読み書きが容易にデータ競合やその他の良くないことを引き起こしてしまうので、それらがすべてunsafeになってしまいます。`static mut`を使うことも、[それを削除しようという提案][remove static mut]すらあることを考えると、できる限り避けたいです。しかし他に方法はあるのでしょうか?不変静的変数を[RefCell]や、果ては[UnsafeCell]のような、[内部可変性][interior mutability]を提供するcell型と一緒に使うという事も考えられます。しかし、それらの型は(ちゃんとした理由があって)[Sync]ではないので、静的変数で使うことはできません。
[mutable static]: https://doc.rust-jp.rs/book-ja/ch19-01-unsafe-rust.html#可変で静的な変数にアクセスしたり変更する
[remove static mut]: https://internals.rust-lang.org/t/pre-rfc-remove-static-mut/1437
[RefCell]: https://doc.rust-jp.rs/book-ja/ch15-05-interior-mutability.html#refcelltで実行時に借用を追いかける
[UnsafeCell]: https://doc.rust-lang.org/nightly/core/cell/struct.UnsafeCell.html
[interior mutability]: https://doc.rust-jp.rs/book-ja/ch15-05-interior-mutability.html
[Sync]: https://doc.rust-lang.org/nightly/core/marker/trait.Sync.html
### スピンロック
同期された内部可変性を得るためには、標準ライブラリを使えるなら[Mutex]を使うことができます。これは、リソースがすでにロックされていた場合、スレッドをブロックすることにより相互排他性を提供します。しかし、私達の初歩的なカーネルにはブロックの機能はもちろんのこと、スレッドの概念すらないので、これも使うことはできません。しかし、コンピュータサイエンスの世界には、OSを必要としない非常に単純なmutexが存在するのです:それが[スピンロック][spinlock]です。スピンロックを使うと、ブロックする代わりに、スレッドは単純にリソースを何度も何度もロックしようとすることで、mutexが開放されるまでの間CPU時間を使い尽くします。
[Mutex]: https://doc.rust-lang.org/nightly/std/sync/struct.Mutex.html
[spinlock]: https://ja.wikipedia.org/wiki/スピンロック
スピンロックによるmutexを使うには、[spinクレート][spin crate]への依存を追加すればよいです:
[spin crate]: https://crates.io/crates/spin
```toml
# in Cargo.toml
[dependencies]
spin = "0.5.2"
```
すると、スピンを使ったMutexを使うことができ、静的な`WRITER`に安全な[内部可変性][interior mutability]を追加できます。
```rust
// in src/vga_buffer.rs
use spin::Mutex;
...
lazy_static! {
pub static ref WRITER: Mutex = Mutex::new(Writer {
column_position: 0,
color_code: ColorCode::new(Color::Yellow, Color::Black),
buffer: unsafe { &mut *(0xb8000 as *mut Buffer) },
});
}
```
`print_something`関数を消して、`_start`関数から直接出力しましょう:
```rust
// in src/main.rs
#[no_mangle]
pub extern "C" fn _start() -> ! {
use core::fmt::Write;
vga_buffer::WRITER.lock().write_str("Hello again").unwrap();
write!(vga_buffer::WRITER.lock(), ", some numbers: {} {}", 42, 1.337).unwrap();
loop {}
}
```
`fmt::Write`トレイトの関数を使うためには、このトレイトをインポートする必要があります。
### 安全性
コードにはunsafeブロックが一つ(`0xb8000`を指す参照`Buffer`を作るために必要なもの)しかないことに注目してください。その後は、すべての命令が安全です。Rustは配列アクセスにはデフォルトで境界チェックを行うので、間違ってバッファの外に書き込んでしまうことはありえません。よって、必要とされる条件を型システムにすべて組み込んだので、安全なインターフェースを外部に提供できます。
### printlnマクロ
大域的なwriterを手に入れたので、プログラムのどこでも使える`println`マクロを追加できます。Rustの[マクロの構文][macro syntax]はすこしややこしいので、一からマクロを書くことはしません。代わりに、標準ライブラリで[`println!`マクロ][`println!` macro]のソースを見てみます:
[macro syntax]: https://doc.rust-lang.org/nightly/book/ch19-06-macros.html#declarative-macros-with-macro_rules-for-general-metaprogramming
[`println!` macro]: https://doc.rust-lang.org/nightly/std/macro.println!.html
```rust
#[macro_export]
macro_rules! println {
() => (print!("\n"));
($($arg:tt)*) => (print!("{}\n", format_args!($($arg)*)));
}
```
マクロは1つ以上のルールを使って定義されます(`match`アームと似ていますね)。`println`には2つのルールがあります:1つ目は引数なし呼び出し(例えば `println!()`)のためのもので、これは`print!("\n")`に展開され、よってただ改行を出力するだけになります。2つ目のルールはパラメータ付きの呼び出し(例えば`println!("Hello")`や `println!("Number: {}", 4)`)のためのものです。これも`print!`マクロの呼び出しへと展開され、すべての引数に加え、改行`\n`を最後に追加して渡します。
`#[macro_export]`属性はマクロを(その定義されたモジュールだけではなく)クレート全体および外部クレートで使えるようにします。また、これはマクロをクレートルートに置くため、`std::macros::println`の代わりに`use std::println`を使ってマクロをインポートしないといけないということを意味します。
[`print!`マクロ][`print!` macro]は以下のように定義されています:
[`print!` macro]: https://doc.rust-lang.org/nightly/std/macro.print!.html
```rust
#[macro_export]
macro_rules! print {
($($arg:tt)*) => ($crate::io::_print(format_args!($($arg)*)));
}
```
このマクロは`io`モジュール内の[`_print`関数][`_print` function]の呼び出しへと展開しています。[`$crate`という変数][`$crate` variable]は、他のクレートで使われた際、`std`へと展開することによって、マクロが`std`クレートの外側で使われたとしてもうまく動くようにしてくれます。
[`format_args`マクロ][`format_args` macro]が与えられた引数から[fmt::Arguments]型を作り、これが`_print`へと渡されています。libstdの[`_print`関数]は`print_to`を呼び出すのですが、これは様々な`Stdout`デバイスをサポートいているためかなり煩雑です。ここではただVGAバッファに出力したいだけなので、そのような煩雑な実装は必要ありません。
[`_print` function]: https://github.com/rust-lang/rust/blob/29f5c699b11a6a148f097f82eaa05202f8799bbc/src/libstd/io/stdio.rs#L698
[`$crate` variable]: https://doc.rust-lang.org/1.30.0/book/first-edition/macros.html#the-variable-crate
[`format_args` macro]: https://doc.rust-lang.org/nightly/std/macro.format_args.html
[fmt::Arguments]: https://doc.rust-lang.org/nightly/core/fmt/struct.Arguments.html
VGAバッファに出力するには、`println!`マクロと`print!`マクロをコピーし、独自の`_print`関数を使うように修正してやればいいです:
```rust
// in src/vga_buffer.rs
#[macro_export]
macro_rules! print {
($($arg:tt)*) => ($crate::vga_buffer::_print(format_args!($($arg)*)));
}
#[macro_export]
macro_rules! println {
() => ($crate::print!("\n"));
($($arg:tt)*) => ($crate::print!("{}\n", format_args!($($arg)*)));
}
#[doc(hidden)]
pub fn _print(args: fmt::Arguments) {
use core::fmt::Write;
WRITER.lock().write_fmt(args).unwrap();
}
```
元の`println`の定義と異なり、`print!`マクロの呼び出しにも`$crate`をつけるようにしています。これにより、`println`だけを使いたいと思ったら`print!`マクロもインポートしなくていいようになります。
標準ライブラリのように、`#[macro_export]`属性を両方のマクロに与え、クレートのどこでも使えるようにします。このようにすると、マクロはクレートの名前空間のルートに置かれるので、`use crate::vga_buffer::println`としてインポートするとうまく行かないことに注意してください。代わりに、 `use crate::println`としなければいけません。
`_print`関数は静的な`WRITER`をロックし、その`write_fmt`メソッドを呼び出します。このメソッドは`Write`トレイトのものなので、このトレイトもインポートしないといけません。最後に追加した`unwrap()`は、画面出力がうまく行かなかったときパニックします。しかし、`write_str`は常に`Ok`を返すようにしているので、これは起きないはずです。
マクロは`_print`をモジュールの外側から呼び出せる必要があるので、この関数は公開されていなければなりません。しかし、これは非公開の実装の詳細であると考え、[`doc(hidden)`属性][`doc(hidden)` attribute]をつけることで、生成されたドキュメントから隠すようにします。
[`doc(hidden)` attribute]: https://doc.rust-lang.org/nightly/rustdoc/write-documentation/the-doc-attribute.html#hidden
### `println`を使ってHello World
こうすることで、`_start`関数で`println`を使えるようになります:
```rust
// in src/main.rs
#[no_mangle]
pub extern "C" fn _start() {
println!("Hello World{}", "!");
loop {}
}
```
マクロはすでに名前空間のルートにいるので、main関数内でマクロをインポートしなくても良いということに注意してください。
期待通り、画面に Hello World! と出ています:

### パニックメッセージを出力する
`println`マクロを手に入れたので、これを私達のパニック関数で使って、パニックメッセージとパニックの場所を出力させることができます:
```rust
// in main.rs
/// この関数はパニック時に呼ばれる。
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
println!("{}", info);
loop {}
}
```
`panic!("Some panic message");`という文を`_start`関数に書くと、次の出力を得ます:

つまり、パニックが起こったということだけでなく、パニックメッセージとそれがコードのどこで起こったかまで知ることができます。
## まとめ
この記事では、VGAテキストバッファの構造と、どのようにすれば`0xb8000`番地におけるメモリマッピングを通じてそれに書き込みを行えるかを学びました。このメモリマップされたバッファへの書き込みというunsafeな操作をカプセル化し、安全で便利なインターフェースを外部に提供するRustモジュールを作りました。
また、cargoのおかげでサードパーティのライブラリへの依存関係を簡単に追加できることも分かりました。`lazy_static`と`spin`という2つの依存先は、OS開発においてとても便利であり、今後の記事においても使っていきます。
## 次は?
次の記事ではRustに組み込まれている単体テストフレームワークをセットアップする方法を説明します。その後、この記事のVGAバッファモジュールに対する基本的な単体テストを作ります。