Don't Repeat Yourself

Don't Repeat Yourself (DRY) is a principle of software development aimed at reducing repetition of all kinds. -- wikipedia

Rust の lang_item について

lang_item とは具体的には rustc の下記の箇所で設定されている言語固有のアイテムのことを指しています。また、#[lang = "<lang_item_name>"] アトリビュートを使用することで、その lang_item の実装を書き換えてしまうことができます。

lang_item の一覧: rust/lang_items.rs at 1ca100d0428985f916eea153886762bed3909771 · rust-lang/rust · GitHub

公式ドキュメントによると、rustc にはいくつかのカスタマイズ可能な機能があり、それを #[lang = "<lang_item_name>"] を使って関数にマーキングすることによって、その機能はこの関数によってカスタマイズ済みであるということを伝えるためのアトリビュートとのことです。

通常のプログラミング時にこの機能を使用することはほとんどないでしょう。Rust を使って Web サービスを作っている間や、Rust のライブラリを使ってコンパイラを作っている以上は、この機能を使用することはほぼないと思います。しかし、OS を作る際には話は別で、たとえば OS 用のプログラムのエントリポイントを変更したいなどの場合にこの機能を使用します。

代表例としては、先ほど紹介したリンクの中に startpanic_fmt というものがあります。start は、その名の通りエントリポイントの修正をかけることができます。イメージ的には main 関数の箇所を変えられるということです。panic_fmt は、Rust コード上で panic が発生した際にその挙動を制御することができます。

具体的な使用例に関するイメージがあまり湧いていませんが、サンプルコードを元に lang_item についての理解を深めていきましょう :) ちなみにこちらの公式ドキュメントから直接引っ張ってきています。

#![feature(lang_items, core_intrinsics)]
#![feature(start)]
#![no_std]
#![no_main]
use core::intrinsics;

extern crate libc;

#[no_mangle]
pub extern fn main(_argc: i32, _argv: *const *const u8) -> i32 {
    0
}

#[lang = "eh_personality"]
#[no_mangle]
pub extern fn rust_eh_personality() {
}

#[lang = "eh_unwind_resume"]
#[no_mangle]
pub extern fn rust_eh_unwind_resume() {
}

#[lang = "panic_fmt"]
#[no_mangle]
pub extern fn rust_begin_panic(_msg: core::fmt::Arguments,
                               _file: &'static str,
                               _line: u32,
                               _column: u32) -> ! {
    unsafe { intrinsics::abort() }
}

注目するのは #[lang = "panic_fmt"] のところで、こう指定することによって、実行中に panic が発生した際の挙動を書き換えることができます。

しかしちょっとわかりにくいと思うので、私が今作成中のもう1つのサンプルコードを元に挙動を確認してみましょう。

#![feature(lang_items)]
#![no_std]
#![no_main]

static HELLO: &[u8] = b"Hello, World!";

// linux
#[no_mangle]
pub extern "C" fn _start() -> ! {
    let vga_buffer = 0xb8000 as *const u8 as *mut u8;

    for (i, &byte) in HELLO.iter().enumerate() {
        unsafe {
            *vga_buffer.offset(i as isize * 2) = byte;
            *vga_buffer.offset(i as isize * 2 + 1) = 0xb;
        }
    }

    loop {}
}

#[lang = "panic_fmt"]
#[no_mangle]
pub extern "C" fn rust_begin_panic(
    _msg: core::fmt::Arguments,
    _file: &'static str,
    _line: u32,
    _column: u32,
) -> ! {
    loop {}
}

これは現在作成中の OS の元となるコードで、VGA テキストバッファを用いてスクリーン上にテキスト(通常は Hello, World! と出力されます)を出力するだけの簡単なプログラムです。このコードを実行すると次のようになります。

f:id:yuk1tyd:20180212120729p:plain

ちなみに余談ですが、冒頭で #![no_std] によって標準ライブラリをすべて削ぎ落としているので、println マクロは使用できません。

では、#[lang = "panic_fmt"] に出力内容を設定して、実際に _start() 関数の中で panic を起こしてみましょう。

#![feature(lang_items)]
#![no_std]
#![no_main]

static HELLO: &[u8] = b"Hello, World!";
static PANIC: &[u8] = b"Panic has been occurred!";

// linux
#[no_mangle]
pub extern "C" fn _start() -> ! {
    panic!("Panic!");

    loop {}
}

#[lang = "panic_fmt"]
#[no_mangle]
pub extern "C" fn rust_begin_panic(
    _msg: core::fmt::Arguments,
    _file: &'static str,
    _line: u32,
    _column: u32,
) -> ! {
    let vga_buf = 0xb8000 as *const u8 as *mut u8;

    for (i, &byte) in PANIC.iter().enumerate() {
        unsafe {
            *vga_buf.offset(i as isize * 2) = byte;
            *vga_buf.offset(i as isize * 2 + 1) = 0xc;
        }
    }

    loop {}
}

panic の部分には、0xc (明るめの赤) によって、「Panic has been occurred!」という文字列を表示することにしました。結果、

f:id:yuk1tyd:20180212121035p:plain

正しく書き換えが行われたことがわかります。

まとめ

  • lang_item は Rust 固有のアイテムのことで、コンパイラが特別視する要素のこと。
  • lang アトリビュートを用いることで、lang_item 内のさまざまな要素を書き換えることができる。
  • no_std と組み合わせて使用するのが(たぶん)代表的な使い方。

参考