はじめに
hooq は、 ? 演算子1、 return 、末尾の式などに対し、必要に応じてメソッドを挿入できる属性マクロです。
use hooq::hooq;
#[hooq]
#[hooq::method(.map(|v| v * 2))]
fn double(s: &str) -> Result<u32, Box<dyn std::error::Error>> {
let res = s.parse::<u32>()?;
Ok(res)
}
fn double_expanded(s: &str) -> Result<u32, Box<dyn std::error::Error>> {
let res = s.parse::<u32>().map(|v| v * 2)?;
Ok(res)
}
#[test]
fn test() {
assert_eq!(double("21").unwrap(), double_expanded("21").unwrap());
}
fn main() {
println!("double_hooked: {}", double("21").unwrap());
println!("double: {}", double_expanded("21").unwrap());
}
豊富なプリセットがあり、エラーロギング等をお手軽に Result にフックすることができます。
なぜhooqを使うか?
hooqのモチベーションを、次のような Result 型を返す関数を含むソースコードを例に説明します。
use std::error::Error;
fn load_host_and_port() -> Result<String, Box<dyn Error>> {
// Loading APP_HOST
let host = std::env::var("APP_HOST")?;
// Loading APP_PORT
let port = std::env::var("APP_PORT")?;
// Convert to u16
let port: u16 = port.parse()?;
Ok(format!("{host}:{port}"))
}
fn main() -> Result<(), Box<dyn Error>> {
let host_and_port = load_host_and_port()?;
println!("Server is running on: {}", host_and_port);
// snip
Ok(())
}
#[test]
fn test_load_host_and_port() {
unsafe {
std::env::set_var("APP_HOST", "localhost");
std::env::set_var("APP_PORT", "8080");
}
let host_and_port = load_host_and_port().unwrap();
assert_eq!(host_and_port, "localhost:8080");
}
各環境変数を指定して実行するとエラーなく実行されます。
$ APP_HOST=127.0.0.1 APP_PORT=10 cargo run -q
Server is running on: 127.0.0.1:10
このプログラムがエラーとなるように実行してみます。
$ APP_PORT=10 cargo run -q
Error: NotPresent
main 関数が返した Box<dyn Error> の内容が表示され、(おそらく)何かしらの環境変数が足りていないというエラーが発生しています。
しかしこのエラー表示は酷いです!
- どういうコンテキストのなんのエラーなのかがわからない
- エラーが発生した場所がわからない
おそらくこのRustプログラムを書いた人は、きめ細かいエラーハンドリングを含んだフォーマルなアプリケーションを作りたかったのではなく、ちょっとしたカジュアルなCLIツールを作りたかったのだと思います。しかしRustのエラー表示はサボる人には冷たいものです2。
こんな時に使えるのが hooq 属性マクロです!
use std::error::Error;
use hooq::hooq;
#[hooq]
fn load_host_and_port() -> Result<String, Box<dyn Error>> {
// Loading APP_HOST
let host = std::env::var("APP_HOST")?;
// Loading APP_PORT
let port = std::env::var("APP_PORT")?;
// Convert to u16
let port: u16 = port.parse()?;
Ok(format!("{host}:{port}"))
}
#[hooq]
fn main() -> Result<(), Box<dyn Error>> {
let host_and_port = load_host_and_port()?;
println!("Server is running on: {}", host_and_port);
// snip
Ok(())
}
#[test]
fn test_load_host_and_port() {
unsafe {
std::env::set_var("APP_HOST", "localhost");
std::env::set_var("APP_PORT", "8080");
}
let host_and_port = load_host_and_port().unwrap();
assert_eq!(host_and_port, "localhost:8080");
}
#[hooq] を付けるだけであら不思議、エラーのスタックトレースもどきが出力されるようになります!
$ APP_PORT=10 cargo run -q
[mdbook-source-code/index/src/main.rs:8:41] NotPresent
8> std::env::var("APP_HOST")?
|
[mdbook-source-code/index/src/main.rs:21:45] NotPresent
21> load_host_and_port()?
|
Error: NotPresent
どうやら APP_HOST 環境変数が足りていなかったようですね。エラー発生箇所についても、8行目、21行目と伝搬していったということがわかります。
一体 hooq マクロが何をしたのか、それは 後ほど 解説していきます。