この記事では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]