この記事ではRustのproconio::input! macroについて書きます。
documentやtest caseで想定されているユースケースをどうやって実現しているのかを追っていきます。 versionは記事を書いている時点で最新のp-v0.4.3
を対象にしています。
proconio::input!
とは
proconio
とは以下のREADME にあるように競技プログラミングで利用されることを意図したIO libraryです。
Easy IO library for competitive programming.
proconio provides an easy way to read values from stdin (or other source). The main is input! macro.
input!
は出題としてstdinからあたえられる入力を読み取る処理を簡潔に記述できることを意図したmacroです。
atcoderのRustの実行環境(judge環境?)に有志の方々が準備してくださっており、利用されている方もおられるのではないでしょうか。
https://github.com/rust-lang-ja/atcoder-rust-resources/wiki/2020-Update
例えば、出題として、m * nの行列が以下の形式であたえられるときの処理はこう書けます。
こんなstdinが与えられるとして
3
3
1 2 3
4 5 6
7 8 9
input!
に与えられるデータの型とbindしたい変数名を書いておくと、stdinからのread, 型変換までを実行してくれます。
stdinからほしいデータ構造をどう読み取るかは、問題の回答とはまた別の話ともいえるので、この処理を簡潔に記述できる手段があるのはうれしいのではないでしょうか。
ただし、macroなので人によっては抵抗(とかモヤモヤ)をもたれることもあるかもしれません。 ということでmacroの内容を理解して、気持ちよく使えるようになるのが目標です。
proconio::input!
の仕組み
できるだけ単純な処理から追っていきたいので、以下ではミニマムのinput!
macroを作っていきます。source
実際の処理は外部から利用しやすいように書かれていますが、今回は必要な型を1 file(module)に展開します。
まずは一番シンプルな以下の処理がどう実現されているのかを追っていきます。
input!
といいたいところですが、毎回stdinから入力を渡して動作確認するのは面倒ですし、テストも書きづらいです。
input!
には入力を変数経由で渡せる方法も用意されているのでそちらを利用します。
ということで最初に見ていくのはこちら
最初の入力に from <入力変数名>
とすることでこれを入力として扱ってくれます。 OnceSource
については後述。
さっそくinput!
macroを見ていきましょう。
実際にはもう少しarmがあるのですが、今回のケースを扱うにはこれで十分です。
macroについてこの記事で関連する範囲で簡単に補足します。
input!{ ... }
macroに渡した内容がtoken列として、渡される(正確にはtoken tree 列)- 上から各armにマッチするかどうか判定されて、マッチしたらその内容が実行される。(ので、armの順番に意味がある)
$name:type
と書くと、typeに応じたtokenがマッチして、=>
の内側でnameで参照できる@from
の先頭の@
は特別な記号ではなく、リテラルとしてマッチする。(これがあるとマッチする範囲を指定しやすいのかなと思っている)$rest:tt
のtt
はどのtokenにもマッチする$(rest:tt)*
こう書くと、すべてのtoken列にマッチできるので、残り全部を後続の処理にそのまま渡せる(tail的な感じ)- カッコ(
[]
,()
)でくくられているとそれがひとつのtt
としてマッチする。($v:tt
に[i32; 3]
がマッチする)
ということで
input!
がどう展開されていくか見ていきます。結論を先にいうと以下のように展開されます。(cargo expand
しました)
let source = from;
let mut s = source;
let a = read;
まず、input!
の最初にfrom source
を渡しているので (from $source:expr, $($rest:tt)*)
にマッチします。そこで以下のように展開されます。
let mut s = source;
input!
@from
と@rest
を付け加えてさらにinput!
を呼び出します。
次に、(@from [$source:expr] @rest $($rest:tt)*)
にマッチして以下のように展開されます。
input!
@mut
を付け加えて、input!
を呼び出します。
これが (@from [$source:expr] @mut [$($mut:tt)?] @rest $var:tt: $($rest:tt)*)
にマッチします。
@mut []
のように@mut
にはなにも渡していませんが、 @mut [$($mut:tt)?]
とrepeatが?
になっているのでマッチします。
さらにこのarmには、@rest $var:tt: $($rest:tt)*
と書かれていて、変数名として渡したa
が$var
にマッチしています。$var:tt
ではなく$var:tt:
であることに注意してください。(最後にコロンがある)。このコロンのリテラルによって、$rest
には変数a
の型が先頭にくるように調整されています。このarmではさらに以下の呼び出しに展開されます。
input!
@kind
が付与された呼び出しはどのarmにマッチするでしょうか。@kind
が書かれているarmは3つあります。
=> // (1)
=> // (2)
=> // (3)
上から(1),(2),(3)とします。今回は@rest u8,
としてよびだしているので、@rest
で終わっている(1)にはマッチしません。
(2)は @rest,
と先頭が,
(カンマ)になっているのでマッチしません。ということで、(3)にマッチします。
ということで、(3)は以下のように展開されます。
input !
いよいよ変数a
がkind [u8]
でparseされそうですね。さらに@ rest,
となっているので、今度はさきほどの(2)にマッチします。 (2)の中で以下のように展開され、(1)にマッチします。
input ! ;
あらためて(1)の定義はこうなっていて
=> ;
こんなふうに展開されます
=>
まだ、read_value!
についてはふれていませんが、このmacroに入力のsource
と型u8
を渡した結果が変数a
にbindされました。
read_value!
にはいる前に、(2)ではinput!
を2回呼び出しています。
// (2)
=> ;
1回目のinput!
はread_value!
に展開され、残った処理はあらためて、@from [$source] @rest $($rest)*
の形で呼ばれます。
ただし今回渡している、a: u8,
はすでに処理されているので、 $($rest)*
は空に展開されます。それがarmの先頭に定義されている
(@from [$source:expr] @rest) => {};
にマッチされ、input!
呼び出しの展開が終了します。
ということで、read_value ! (@ source [&mut s] @ kind [u8])
この処理が入力からu8
型を呼んで返せば、input!
が完了したことになります。
read_value!
でinput!
呼び出し側から渡される任意の型の読み取り処理をおこないます。今回は a: u8,
なのでそれを処理するためだけなら以下のarmのみで対応できます。
ということで、$kind:ty
で渡される型がReadable
traitを実装していることを前提にして、Readable::read()
を呼び出しています。 Readable
は以下のように定義されています。
Source
から自身を生成できる型がReadable
という感じでしょうか。ということでSource
の定義ですが以下のようになっています。
今回の例では、source
として、OnceSource
を渡していました。OnceSource
は概ね、入力(stdinだったり、&'static str)をwrapして、データの区切り(atcoderではwhite spaceとnewline)を隠蔽する役割をもっています。
入力をheapに確保しつつ、mem::transmute()
して、'static
なlifetimeに変換してwhite spaceをdelimiterとしてiteratorを保持している感じでしょうか。
Sourceがなんとなくわかったので、最後はu8
のReadable
実装です。要はstringから自身を生成できればよいので、str::FromStr
と同じ状況といえます。なので以下のように、str::FromStr
を実装している型はReadable
になります。
ここにきて、input!
がparse::<u8>().unwrap()
を代わりにに呼んでくれていることがわかりました。
(余談ですが、std::any::type_name()
はこういうところで使われるんですね)
ここまででようやく、input!
の処理を通しで追うことができました。最終的にはこれが
こうなりました。
今回は、u8
でしたが、str::FromStr
を実装している型はReadable
なので、他の基本的な型も同じです。
次は[i32; 3]
のような配列/sliceをみていきたいのですが、まだ標準入力から読む処理をみていなかったので、その場合のarmを追加しましょう。
stdinから読む場合
以下のarmを追加するだけです。
! lazy_static
macro_rules!
input!
からsourceをもらうかわりに、std::io::Stdin
からOnceSource
を生成して、後続のinput!
に渡しています。以降の処理はfrom source
で渡したときと同様です。
一番上のレイヤーで入力がどこからきたかを吸収していて、非常に参考になります。
ちなみに実際の処理はもうひとひねりされおりまして、以下のようになっています。
lazy_static!
https://github.com/statiolake/proconio-rs/blob/a3b10f55c15177ec322ce686f0bd977011593366/proconio/src/lib.rs#L525-L529
さきほどは、OnceSource
だった箇所がAutoSource
になっています。さらにAutoSource
は以下のようになっています。
https://github.com/statiolake/proconio-rs/blob/a3b10f55c15177ec322ce686f0bd977011593366/proconio/src/source/mod.rs#L63-L72
atcoder上では提出したコードはrelease buildされるはずだったので、結果的にOnceSource
が実行されることになります。
さらに、OnceSource
の生成処理は以下のようになっており、lazy_static
で一度だけ走る初期化処理時に入力をすべてメモリに保持します。
なので、input!
を複数回呼び出しても、読み込み処理が複数回走るわけではなさそうです。
macroのdebug
ここまでで、一番シンプルなケースでinput!
がどう機能するか見てきました。以降はこのinput!
を拡張しながら他のユースケースにも対応できるようにしていきます。
ただその前に、macroの展開debugする方法について書いておきます。以降はこのdebugの出力をみながら進めていきます。
// ...
#![feature(trace_macros)]
を有効化して、input!
の前後にtrace_macros!
を追加します。
cargo +nightly run
のようにnightly
で実行すると、以下の出力を得ます。
)
|
|
| |
|
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
)
input!
からinput!
を呼び出しているので、慣れないと見づらいですが自分は以下のように読んでいます。
expanding
がmacro呼び出し時のtrace。@
の位置を参考にどのarmにマッチするかはここで判断できる。
to
がマッチしたarmの展開内容、後続のmacro呼び出し時にどのように展開されたかがわかる。
このtraceをおっていくとさきほどまでみてきた流れと同じだとわかります。
最終的な展開結果がみたい場合は、cargo expand
を利用しました。
mut
入力をbindする変数をmut
にしておくこともできます。今回もarmをひとつ追加するだけです。
mut
をリテラルであてて、後続の処理に渡しています。
=> ;
最終的に、変数宣言時にmut
が付与されます。
marker::{Bytes,Chars}
これまでみてきたように、入力からの変換処理は、input!
に指定した型のReadable::read()
の実装次第です。(str::FromStr
を実装していれば、FromStr::from_str()
がよばれる)
そのため、入力をString
として受け取りたければ、String
がそのまま利用できます。
input!
これは、String
がFromStr
を実装しているためです。
入力をVec<char>
や、Vec<u8>
でうけとりたいときはどうすればよいでしょうか。型定義して、Readable
を実装すればよいのですが、想定されるユースケースなので、proconio
側が用意してくれています。それが、marker::{Chars,Bytes}
です。 [https://github.com/statiolake/proconio-rs/blob/master/proconio/src/marker.rs]
それぞれがReadable
を定義しているので、以下のようにかけます。 debugを抜粋すると以下のようになっているのがわかります。
= note: to `< String as Readable > :: read(&mut s)`
= note: to `< Chars as Readable > :: read(&mut s)`
= note: to `< Bytes as Readable > :: read(&mut s)`
marker::{Usize1,Isize1}
1オリジンの数を0オリジンに変換するutilityを提供してくれます。処理としてはread時にchecked_sub()
を実行してくれています
array
次に、arrayの読み込みについて見ていきます。最初にarrayの要素数、次に要素数に対応した各要素が渡される入力を想定しています。
# !
// ...
実はinput!
macro自体はさきほどのmut対応で、完成し、以後はread_value!
の拡張で対応できる型を増やしていきます。どういうことかというと、debugの抜粋ですが以下のような出力をえます。(n: usize,
が処理されたあとを想定してください)
= note: expanding `input! { @ from [&mut s] @ rest a : [i32 ; n], }`
= note: to `input ! { @ from [&mut s] @ mut [] @ rest a : [i32 ; n], }`
= note: expanding `input! { @ from [&mut s] @ mut [] @ rest a : [i32 ; n], }`
= note: to `input ! { @ from [&mut s] @ mut [] @ var a @ kind [] @ rest [i32 ; n], }`
// ここがポイント
= note: expanding `input! { @ from [&mut s] @ mut [] @ var a @ kind [] @ rest [i32 ; n], }`
= note: to `input ! (@ from [&mut s] @ mut [] @ var a @ kind [[i32 ; n]] @ rest,) ;`
// ↑ `[i32 ; n] がttとして扱われている
//
= note: expanding `input! { @ from [&mut s] @ mut [] @ var a @ kind [[i32 ; n]] @ rest, }`
= note: to `input ! (@ from [&mut s] @ mut [] @ var a @ kind [[i32 ; n]] @ rest) ; input !
(@ from [&mut s] @ rest) ;`
= note: expanding `input! { @ from [&mut s] @ mut [] @ var a @ kind [[i32 ; n]] @ rest }`
= note: to `let a = read_value ! (@ source [&mut s] @ kind [[i32 ; n]]) ;`
ここがポイントなのですが、a: [i32; n]
という入力がmacroにはtoken treesとして渡されます。擬似的にひとつのtoken treeを{}
でくくるとすると以下のようになります。
{a} {:} {[i32 ; n]} {,}
その結果、[i32 ; n]
がひとつの型情報として扱わ、read_value ! (@ source [&mut s] @ kind [[i32 ; n]]) ;
として、read_value!
にarrayとして渡されます。(nはparse済)
したがって、read_value!
でarrayを渡されても値をかえせるようにすればよいことになります。
arrayに対応するarmを追加したあとのread_value!
は以下のようになります。
ポイントは(@source [$source:expr] @kind [[$($kind:tt)*]])
armで、[[ ... ]]
でうけることで、[i32 ; n ]
を再び、{i32}, {;}, {n}
のようにtoken tree列にしているところです。
こうすることで、最終的にi32
をn
回readするloopが生成されています。
= note: expanding `input! { @ from [&mut s] @ mut [] @ var a @ kind [[i32 ; n]] @ rest }`
= note: to `let a = read_value ! (@ source [&mut s] @ kind [[i32 ; n]]) ;`
= note: expanding `read_value! { @ source [&mut s] @ kind [[i32 ; n]] }`
= note: to `read_value ! (@ array @ source [&mut s] @ kind [] @ rest i32 ; n)`
= note: expanding `read_value! { @ array @ source [&mut s] @ kind [] @ rest i32 ; n }`
= note: to `read_value ! (@ array @ source [&mut s] @ kind [i32] @ rest ; n)`
= note: expanding `read_value! { @ array @ source [&mut s] @ kind [i32] @ rest ; n }`
= note: to `read_value ! (@ array @ source [&mut s] @ kind [i32] @ len [n])`
= note: expanding `read_value! { @ array @ source [&mut s] @ kind [i32] @ len [n] }`
= note: to `{
let len = n ; (0 .. len) .
map(| _ | read_value ! (@ source [&mut s] @ kind [i32])) . collect :: <
Vec < _ >> ()
}`
= note: expanding `read_value! { @ source [&mut s] @ kind [i32] }`
= note: to `< i32 as Readable > :: read(&mut s)`
expandしてみると以下のようなコードになっています。
ここから、input!
にa: [i32 ; 3]
と渡してもarrayではなくVec<i32>
が生成されることもわかりますね。
2d array
arrayを読み込めるようになったので次に2d arrayを読み込めるようにします。なんとmacroは特に追加することなく既に対応できています。
# !
// ...
今回はdebugをすべてのせます
❯ c +nightly run
note: trace_macro
--> src/main.rs:272:5
|
272 | / input! {
273 | | from source,
274 | | m: [[i32; 3]; 2],
275 | | }
| |_____^
|
= note: expanding `input! { from source, m : [[i32 ; 3] ; 2], }`
= note: to `let mut s = source ; input ! { @ from [& mut s] @ rest m : [[i32 ; 3] ; 2], }`
= note: expanding `input! { @ from [& mut s] @ rest m : [[i32 ; 3] ; 2], }`
= note: to `input ! { @ from [&mut s] @ mut [] @ rest m : [[i32 ; 3] ; 2], }`
= note: expanding `input! { @ from [&mut s] @ mut [] @ rest m : [[i32 ; 3] ; 2], }`
= note: to `input ! { @ from [&mut s] @ mut [] @ var m @ kind [] @ rest [[i32 ; 3] ; 2], }`
= note: expanding `input! { @ from [&mut s] @ mut [] @ var m @ kind [] @ rest [[i32 ; 3] ; 2], }`
= note: to `input ! (@ from [&mut s] @ mut [] @ var m @ kind [[[i32 ; 3] ; 2]] @ rest,) ;`
= note: expanding `input! { @ from [&mut s] @ mut [] @ var m @ kind [[[i32 ; 3] ; 2]] @ rest, }`
= note: to `input ! (@ from [&mut s] @ mut [] @ var m @ kind [[[i32 ; 3] ; 2]] @ rest) ;
input ! (@ from [&mut s] @ rest) ;`
= note: expanding `input! { @ from [&mut s] @ mut [] @ var m @ kind [[[i32 ; 3] ; 2]] @ rest }`
= note: to `let m = read_value ! (@ source [&mut s] @ kind [[[i32 ; 3] ; 2]]) ;`
= note: expanding `read_value! { @ source [&mut s] @ kind [[[i32 ; 3] ; 2]] }`
= note: to `read_value ! (@ array @ source [&mut s] @ kind [] @ rest [i32 ; 3] ; 2)`
= note: expanding `read_value! { @ array @ source [&mut s] @ kind [] @ rest [i32 ; 3] ; 2 }`
= note: to `read_value ! (@ array @ source [&mut s] @ kind [[i32 ; 3]] @ rest ; 2)`
= note: expanding `read_value! { @ array @ source [&mut s] @ kind [[i32 ; 3]] @ rest ; 2 }`
= note: to `read_value ! (@ array @ source [&mut s] @ kind [[i32 ; 3]] @ len [2])`
= note: expanding `read_value! { @ array @ source [&mut s] @ kind [[i32 ; 3]] @ len [2] }`
= note: to `{
let len = 2 ; (0 .. len) .
map(| _ | read_value ! (@ source [&mut s] @ kind [[i32 ; 3]])) . collect
:: < Vec < _ >> ()
}`
= note: expanding `read_value! { @ source [&mut s] @ kind [[i32 ; 3]] }`
= note: to `read_value ! (@ array @ source [&mut s] @ kind [] @ rest i32 ; 3)`
= note: expanding `read_value! { @ array @ source [&mut s] @ kind [] @ rest i32 ; 3 }`
= note: to `read_value ! (@ array @ source [&mut s] @ kind [i32] @ rest ; 3)`
= note: expanding `read_value! { @ array @ source [&mut s] @ kind [i32] @ rest ; 3 }`
= note: to `read_value ! (@ array @ source [&mut s] @ kind [i32] @ len [3])`
= note: expanding `read_value! { @ array @ source [&mut s] @ kind [i32] @ len [3] }`
= note: to `{
let len = 3 ; (0 .. len) .
map(| _ | read_value ! (@ source [&mut s] @ kind [i32])) . collect :: <
Vec < _ >> ()
}`
= note: expanding `read_value! { @ source [&mut s] @ kind [i32] }`
= note: to `< i32 as Readable > :: read(&mut s)`
= note: expanding `input! { @ from [&mut s] @ rest }`
= note: to ``
Finished dev [unoptimized + debuginfo] target(s) in 0.00s
Running `target/debug/proconio-handson`
[[1, 2, 3], [4, 5, 6]]
さきほどarrayで述べたように、[...]
はひとつのtoken treeとして扱われるので、read_value!
へは、@ kind [ [[i32 ; 3] ; 2] ])
として、[[i32; 3]; 2]
という2d arrayの型情報をたもったまま扱われます。 ここが2d arrayのポイントなのですが、read_value!
は先頭のarmで@kind [[$($kind:tt)*]]
のようにマッチさせているので、@rest
として、[i32 ; 3] ; 2
が残ります。arrayのところでは、read_value!
は i32 ; 3
のような入力をうまく扱えましたが、これは型 ; len
という順番のtoken treeともいえます。[i32 ; 3] ; 2
であっても、[]
でグルーピングされひとつのtoken treeとして扱われるので、[i32 ; 3](型) ; 2(len)
と同じ並びといえます。したがって、i32
を3回readするコードが生成されたのと同じ理由で、[i32 ; 3]
を2回readするコードが生成されます。 expandしてみると
let source = from;
input!
から
というコードが生成されました。
ということで、再帰的に処理されるので
input!
のように拡張できることもわかりました。
slice
documentではjagged arrayとして書かれているsliceの読み込みについて
If the first input is the length of the array, you can omit the length. This is the only way to read jagged array (an array of arrays of which the member arrays can be of different sizes) at once.
入力が2 X Y
のように要素数 要素..要素の場合、input!
にsliceを渡すことができます。
# !
// ...
ここはポイントだけかいつまんで話すと[i32]
がread_value
に以下のように渡されます。 = note: expanding read_value! { @ source [&mut s] @ kind [[i32]] }
そして、このarmにマッチします。
=> ;
このため、read_value!
が要素数が渡されている前提でlen
を取得する処理が生成され、要素数分readするloopが生成されます。
tuple
tupleの場合はどうでしょうか。シンプルな要素数2のタプルを読む処理でみていきます。
# !
// ...
tupleもarray同様に、()
でグルーピングされ、read_value!
に渡されます。tupleのために追加するarmは以下です。
;
=> ;
=> ;
=> ;
=> ;
=> ;
}
arrayでは、@kind [[$($kind:tt)*]]
でマッチさせていたところが、@kind [($($kinds:tt)*)]
になっています。arrayと違うのは、tupleの要素の型を順番に処理して最終的には以下のようなコードを生成しているところです。
let t = ;
debugしてみると以下の出力をえます
❯ c +nightly run
Compiling proconio-handson v0.1.0 (/Users/ymgyt/rs/proconio-handson)
note: trace_macro
--> src/main.rs:366:5
|
366 | / input! {
367 | | from source,
368 | | t: (i8, String),
369 | | }
| |_____^
|
= note: expanding `input! { from source, t : (i8, String), }`
= note: to `let mut s = source ; input ! { @ from [& mut s] @ rest t : (i8, String), }`
= note: expanding `input! { @ from [& mut s] @ rest t : (i8, String), }`
= note: to `input ! { @ from [&mut s] @ mut [] @ rest t : (i8, String), }`
= note: expanding `input! { @ from [&mut s] @ mut [] @ rest t : (i8, String), }`
= note: to `input ! { @ from [&mut s] @ mut [] @ var t @ kind [] @ rest(i8, String), }`
= note: expanding `input! { @ from [&mut s] @ mut [] @ var t @ kind [] @ rest(i8, String), }`
= note: to `input ! (@ from [&mut s] @ mut [] @ var t @ kind [(i8, String)] @ rest,) ;`
= note: expanding `input! { @ from [&mut s] @ mut [] @ var t @ kind [(i8, String)] @ rest, }`
= note: to `input ! (@ from [&mut s] @ mut [] @ var t @ kind [(i8, String)] @ rest) ;
input ! (@ from [&mut s] @ rest) ;`
= note: expanding `input! { @ from [&mut s] @ mut [] @ var t @ kind [(i8, String)] @ rest }`
= note: to `let t = read_value ! (@ source [&mut s] @ kind [(i8, String)]) ;`
= note: expanding `read_value! { @ source [&mut s] @ kind [(i8, String)] }`
= note: to `read_value !
(@ tuple @ source [&mut s] @ kinds [] @ current [] @ rest i8, String)`
= note: expanding `read_value! { @ tuple @ source [&mut s] @ kinds [] @ current [] @ rest i8, String }`
= note: to `read_value !
(@ tuple @ source [&mut s] @ kinds [] @ current [i8] @ rest, String)`
= note: expanding `read_value! { @ tuple @ source [&mut s] @ kinds [] @ current [i8] @ rest, String }`
= note: to `read_value !
(@ tuple @ source [&mut s] @ kinds [[i8]] @ current [] @ rest String)`
= note: expanding `read_value! { @ tuple @ source [&mut s] @ kinds [[i8]] @ current [] @ rest String }`
= note: to `read_value !
(@ tuple @ source [&mut s] @ kinds [[i8]] @ current [String] @ rest)`
= note: expanding `read_value! { @ tuple @ source [&mut s] @ kinds [[i8]] @ current [String] @ rest }`
= note: to `read_value !
(@ tuple @ source [&mut s] @ kinds [[i8] [String]] @ current [] @ rest)`
= note: expanding `read_value! { @ tuple @ source [&mut s] @ kinds [[i8] [String]] @ current [] @ rest }`
= note: to `(read_value ! (@ source [&mut s] @ kind [i8]), read_value !
(@ source [&mut s] @ kind [String]),)`
= note: expanding `read_value! { @ source [&mut s] @ kind [i8] }`
= note: to `< i8 as Readable > :: read(&mut s)`
= note: expanding `read_value! { @ source [&mut s] @ kind [String] }`
= note: to `< String as Readable > :: read(&mut s)`
= note: expanding `input! { @ from [&mut s] @ rest }`
= note: to ``
Finished dev [unoptimized + debuginfo] target(s) in 0.37s
Running `target/debug/proconio-handson`
t: (10, "X")
@current
を経由して、一つづつtupleの型を処理して、すべて処理したら(@current []
) tupleの生成表現に変換しています。 2d arrayと同様に、tupleもarrayの要素型にできます。
複数回よびだし
OnceSource
のところでみたようにinput!
は最初の呼び出しで、入力をすべてメモリに読み込んでlazy_static
経由でglobalに保持するので2回目以降の呼び出しでも前回呼び出しの状態から読み込むことができます。そのため、入力の構造にあわせて適宜loopの中でもよべるようになっています。
まとめ
proconio::input!
macroがどうやって入力を読み込む処理を実現しているのかをみてきました。
input!
と書いたときのモヤモヤがすこしでも軽減されればうれしいです。
シンプルなarmを組み合わせて、任意な型の読み込み処理を行うコードが生成されており、とてもすごいと思いました。 今回は触れられませんでしたが、User定義型に自動で、Readable
の実装を生成する#[derive_readable]
についてもそのうち追ってみようと思っています。 こういった環境を整備してくださっている有志の皆様にはいつも大変感謝しております。 (proconio
以外にもいろいろなコードがatcoder上で使えます ) [https://github.com/rust-lang-ja/atcoder-rust-resources/wiki/2020-Update:embed:cite]