マクロで書く必要がない部分もマクロで書いてるのでtraitに直してみる
use std::io::stdin;
use std::str::FromStr;
use std::error::Error;
pub type Result<T> = ::std::result::Result<T, Box<Error>>;
pub trait Get: Sized {
fn get() -> Result<Self>;
fn getn(n: usize) -> Result<Vec<Self>> {
(0..n).map(|_| Self::get()).collect()
}
}
fn read() -> Vec<String> {
let mut line = String::new();
stdin().read_line(&mut line).unwrap();
line.split_whitespace().map(|s| s.to_string()).collect()
}
impl<T: FromStr> Get for (T,)
where
T::Err: 'static + Error,
{
fn get() -> Result<Self> {
Ok((read()[0].trim().parse()?,))
}
}
impl<T1: FromStr, T2: FromStr> Get for (T1, T2)
where
T1::Err: 'static + Error,
T2::Err: 'static + Error,
{
fn get() -> Result<Self> {
let val = read();
Ok((val[0].trim().parse()?, val[1].trim().parse()?))
}
}
fn main() {
let (a,) = <(usize,)>::get().unwrap();
let (b, c) = <(u32, f32)>::get().unwrap();
println!("a = {}", a);
println!("b = {}", b);
println!("c = {}", c);
}
これで以下のように実行できる:
cargo run << EOF
2
3 1.1
EOF
出力:
a = 2
b = 3
c = 1.1
よくわかる解説
※特にわかりやすくはありません
let a = <usize>::get().unwrap();
let (b, c) = <(u32, f32)>::get().unwrap();
方針としてはまずこの形で書けることを想定します。元のネタでq
個取得するのがあるのでgetn(q: usize)
関数もあります。Iterator<Result<T>>
はResult<Vec<T>>
にcollect()
できるので簡単に書けますね。
エラーはget()
内でunwrap()
してしまってもいいですが、Box<Error>
として適当に括るのもいいでしょう:
use std::error::Error;
pub type Result<T> = ::std::result::Result<T, Box<Error>>;
さて、get()
をどうやって書くかです。様々な型に読み込む必要があるため、get() -> Self
型の関数を持つtraitを定義しましょう:
pub trait Get: Sized {
fn get() -> Result<Self>;
...
}
Self
自身が戻り値になるのでSelf: Sized
である必要があります。
一つの場合には落とし穴があるので、まず2つの場合を考えてみましょう:
impl<T1: FromStr, T2: FromStr> Get for (T1, T2)
where
T1::Err: 'static + Error,
T2::Err: 'static + Error,
{ ... }
なにかゴテゴテしいのが来ましたね(´・ω・`)
でも特に難しいわけではないので安心してください。まずString
からパースするにはT: FromStr
である必要があります:
pub trait FromStr {
type Err;
fn from_str(s: &str) -> Result<Self, Self::Err>;
}
なんかtrait自体にErr
型がついてますね。これは関連型と言って、T: FromStr
なら常にT::Err
という型が定義されていることが保証されているということです。from_str()
関数はパースに失敗した場合、整数型ならParseIntError
、浮動小数ならParseFloatError
を返しますが、この辺の違いをErr
で吸収してるわけです。今はエラーは全部Box<Error>
に変換してしまいたいので、この変換を保証する必要があります。それがT::Err: 'static + Error
の部分で、T::Err
が静的な型で、Error
traitを実装していることを保証する、という制約です。この辺は雰囲気で書いたらコンパイラが付けてって言ってくれます(*'▽')
次に一つの場合を考えてみましょう。
impl<T: FromStr> Get for T
where
T::Err: 'static + Error,
{ ... }
こうしようと思いますよね?でもこれではダメなんです。T
が(T1, T2)
と一致する可能性があるので、この定義は2個の場合の定義と衝突してコンパイルエラーになります。なので要素が一つのタプルとしてマッチさせます:
impl<T: FromStr> Get for (T,)
where
T::Err: 'static + Error,
{ ... }
これだと(T1, T2)
と衝突する可能性がないので無事定義できますが、インターフェースを変える必要があります:
let (a,) = <(usize,)>::get().unwrap();
こういう落とし穴はだいたい2個の場合を書いたときに気付くわけです(´・ω・`)
上の例では2個までしかパースできず、3個以上の値をパースする場合は(T1, T2, T3)
のパーサを書く必要があります。このあたりはC++の可変長テンプレートに慣れてる人は呪いの言葉が出てくるところですが、現状無理なので仕方がありません。このimpl
部分をマクロで生成するのが行儀のよいマクロの使い方かと思います。そうすればライブラリとして外部に見える部分にはマクロが含まれずGet
traitだけが見えることになります。
最後に
マクロは用法用量を守って正しく使いましょう(/・ω・)/