Rust The Embedonomiconをやる④
tomo-wait-for-it-yuki.hatenablog.com
続きです。 最後の7. Global singletonsをやっていきます。
Global singletons
ログ機能をグローバルなオブジェクト経由で実現するように修正します。
log!(logger, "String")
で使用していたログmacroを、log!("String")
で使えるようにします。
#[global_allocator]
に似たような仕組みを作るようです。global_allocatorがどのような仕組みなのか気になっていたので、願ったり叶ったりです。
lob/src/lib.rs
pub trait GlobalLog: Sync { fn log(&self, address: u8); } ... #[macro_export] macro_rules! log { // NEW! ($string:expr) => { unsafe { extern "Rust" { static LOGGER: &'static dyn $crate::GlobalLog; } #[export_name = $string] #[link_section = ".log"] static SYMBOL: u8 = 0; $crate::GlobalLog::log(LOGGER, &SYMBOL as *const u8 as usize as u8) } }; // 今まであった`log!(logger, "String")`スタイルのパターン } #[macro_export] macro_rules! global_logger { ($logger:expr) => { #[no_mangle] pub static LOGGER: &dyn $crate::GlobalLog = &$logger; }; }
なるほど。global_logger
macroでstaticかつglobalなLOGGERを定義しておいて、log
macroでは、そのLOGGERを使ってログ出力するわけですね。
追加したmacroを使ってアプリケーションを作成します。
app/src/main.rs
#![no_main] #![no_std] ... use log::{global_logger, log, GlobalLog}; use rt::entry; struct Logger; global_logger!(Logger); entry!(main); fn main() -> ! { log!("Hello, world!"); log!("Goodbye"); debug::exit(debug::EXIT_SUCCESS); loop {} } impl GlobalLog for Logger { fn log(&self, address: u8) { // we use a critical section (`interrupt::free`) to make the access to the // `static mut` variable interrupt safe which is required for memory safety interrupt::free(|_| unsafe { static mut HSTDOUT: Option<HStdout> = None; // lazy initialization if HSTDOUT.is_none() { HSTDOUT = Some(hio::hstdout()?); } let hstdout = HSTDOUT.as_mut().unwrap(); hstdout.write_all(&[address]) }).ok(); // `.ok()` = ignore errors } }
Logger
構造体を定義し、GlabalLog
traitを実装しています。
なるほどねー、という感じです。
global_allocとも使い方が一緒なので、おおまかにはこんな感じになっている、というのもうなづけます。
おわりに
これで、Embedonomiconシリーズの写経が完了しました。 Rustにおけるリンカの使いこなしや、stableでのアセンブラの使い方など、多くのことを学ぶことができるチュートリアルでした。 正直、やっていてかなり面白かったです。 ただし、Rustをある程度知っていて、低レイヤ(特にリンカ)の知識があることが前提の印象を受けました。そのあたりの解説は、ドキュメント内にありません。
これからお目当てのriscv-rtをハックしていこうと思います。