はじめに
Rust Advent Calendar 12/8 の記事です。
要求された空間や時間内での動作、 デバイスドライバやオペレーティングシステムのような低レベルなコードなど他の言語が苦手とする多数のユースケースを得意とします。
このうたい文句にやられ、9月くらいから Windows API を Rust で呼び出して、パフォーマンス情報を取得するようなコードを書いていました。
ROki1988/perf_client
Windows API の呼び出しについては、すでにあった retep998/winapi-rs を使っています。
で、winapi-rs
は unsafe な関数たちです。
このコードたちをどう Rust の世界へ引き込んだらいいんだろう、と考えたりしたので、
そちらについて触れていきます。
Wrapper を書く
結論として、Wrapper 関数を書くことで対応しました。
fn pdh_open_query() -> Result<winapi::PDH_HQUERY, winapi::PDH_STATUS> {
use std::ptr;
let mut hquery = winapi::INVALID_HANDLE_VALUE;
let ret = unsafe { pdh::PdhOpenQueryW(ptr::null(), 0, &mut hquery) };
if winapi::winerror::SUCCEEDED(ret) {
Ok(hquery)
} else {
Err(ret)
}
}
上記関数はパフォーマンスカウンターの値を取得する PDH* 関数の1つになります。
pdh::PdhOpenQueryW
が winapi-rs
にて定義されている関数です。
書いたり消したりしながら、以下の方針が形になってきました。
- Wrapper 関数内で
unsafe
を完結させる- かつ
unsafe
の範囲をなるべく小さくする
- かつ
- 結果は
Result
の文脈で包む- かつ Wrapper 関数のエラーの型は ERROR_CODE でそろえる
たぶん当たり前のこと書いてますw けどそろえることが大事、としました。
これで Wrapper 関数の呼び出し元では Rust の世界でコードを書いていけます。
impl<'a> Iterator for PdhControllerIterator<'a> {
type Item = PdhCollectValue;
fn next(&mut self) -> Option<Self::Item> {
if self.index == 0 {
pdh_collect_query_data(self.pdhc.hquery);
}
let item = self.pdhc
.items
.get(self.index)
.iter()
.flat_map(|c| {
pdh_get_formatted_counter_value(c.hcounter, PDH_FMT_DOUBLE)
.map(|v| PdhCollectValue::new(&c.element, v))
})
.last();
self.index += 1;
item
}
}
上記コードでは pdh_collect_query_data
と pdh_get_formatted_counter_value
が Wrapper 関数にあたります。
unsafe
は一切見当たらず、Rust の世界で関数をつなげながら、ガシガシ書いていけました。
具体的には Drop トレイトを実装してハンドルの開放をしたり、Iterator トレイトを実装して map で値を取り出せるようにしたりしました。
いやぁ楽しい!
おわりに
Wrapper 関数を書くのは少し手間ですが、Rust の世界の呼び出し元では心地よくコードがかけました。
うたい文句にやられてよかったです。