これはRust その2 Advent Calendarの17日目の記事です。
昨日はtanakhさんの「Run Rust code on PEZY-SC processor」でした。
rust-protobufを利用して、protobuf/examplesの例をRustでも書いてみる、という試み。
昨日はtanakhさんの「Run Rust code on PEZY-SC processor」でした。
rust-protobufを利用して、protobuf/examplesの例をRustでも書いてみる、という試み。
GitHubリポジトリ
環境構築
まずはprotocをインストールする。Ubuntu 16.04で
sudo apt install protobuf-compiler
した場合、libprotoc 2.6.1がインストールされるが、今回使用するaddressbook.protoのために3系を入れ直す。
> sudo apt-get remove protobuf-compiler > wget https://github.com/google/protobuf/releases/download/v3.1.0/protoc-3.1.0-linux-x86_64.zip > unzip protoc-3.1.0-linux-x86_64.zip > sudo mv bin/protoc /usr/local/bin/ > protoc --version libprotoc 3.1.0
rust-protobufのREADMEに従ってprotoc-gen-rustをインストールする。
> cargo install protobuf Updating registry `https://github.com/rust-lang/crates.io-index` Compiling protobuf v1.0.24 warning: unused import, #[warn(unused_imports)] on by default --> /home/zaneli/.cargo/registry/src/github.com-1ecc6299db9ec823/protobuf-1.0.24/src/lib/codegen.rs:3:5 | 3 | use std::io::Write; | ^^^^^^^^^^^^^^ Finished release [optimized] target(s) in 57.9 secs Installing /home/zaneli/.cargo/bin/protoc-gen-rust Installing /home/zaneli/.cargo/bin/protobuf-bin-gen-rust-do-not-use警告が出たがインストールできたようだ。
今回使用するプロジェクトを作成して、protoファイルからRustソースコードを生成する。
> cargo new rust-protobuf-example --bin > cd rust-protobuf-example > wget https://raw.githubusercontent.com/google/protobuf/master/examples/addressbook.proto > protoc --rust_out src/ addressbook.protosrc/addressbook.rs が生成される。
protobuf/examples Python版を動かす
protobuf/examples にあるコードサンプルはどういうものなのか確認してみる。> git clone git@github.com:google/protobuf.git > cd protobuf/examples > make python > pip install protobufとりあえずこれでPython版のサンプルを動かす準備ができた。
対話式で名前や電話番号などの情報を入力してファイルに書き出す
add_person
、ファイルを読み込んでその情報を出力する
list_people
という2つのプログラムがある。使ってみよう。
> ./add_person_python address_py.data address.data: File not found. Creating a new file. Enter person ID number: 1 Enter name: ザネリ Enter email address (blank for none): python@zaneli.com Enter a phone number (or leave blank to finish): 090-1111-2222 Is this a mobile, home, or work phone? Unknown phone type; leaving as default value. Enter a phone number (or leave blank to finish): 090-3333-4444 Is this a mobile, home, or work phone? work Enter a phone number (or leave blank to finish): > ./add_person_python address_py.data Enter person ID number: 2 Enter name: ざねり Enter email address (blank for none): Enter a phone number (or leave blank to finish):
> ./list_people_python address_py.data Person ID: 1 Name: ザネリ E-mail address: python@zaneli.com Mobile phone #: 090-1111-2222 Work phone #: 090-3333-4444 Person ID: 2 Name: ざねりこれと同じように動くコードをRustで書いていこう。
Rust版 list_people
最初に(比較的簡単そうな)list_peopleから実装していく。JavaやPythonのコードを参考にすると、
AddressBook::new()
してparse_from
的な何かをメソッド呼び出しするのかと思ったが、protobuf::parse_from_reader
を呼んでAddressBook
を作るようだ。この辺はprotobuf/examplesの他言語のコードやDocs.rsのドキュメントを調べつつ書いていった。
Rust版 add_person
add_personは標準入力からの文字列取得、電話番号入力のループ、電話番号を繰り返し要素に追加など比較的やる事が多い。
分かってしまえばしょうもないところでいくつかハマったので書き残しておく。
標準入力から改行を除く
email.is_empty()
のように判定していたが意図通り動かず、改行を除く必要があった。email.trim().is_empty()
とした。その他の入力文字列を
Person
に設定していく箇所もtrim()
を呼んでから行う。File::openで読み取りモード・File::createで書き込みモード
これに気づかず、AddressBookの内容をファイルに書き出す箇所でもFile::open(&path)
を渡してしまい、エラーも発生せずファイル書き出しも成功しない現象に陥った。
main.rsで実行するプログラムを選択させる
引数でadd_person
を実行するかlist_people
を実行するかを分けたかったので、そのようにmainを実装する。
get_module_name
で関数を返す箇所やエラーハンドリングをand_then
,unwrap_or_else
で繋げていく箇所などはRustドキュメントのエラーハンドリングの章を参考にしつつ書いてみた。
実装してみて感じた理解があやふやなままの点を挙げておく。
特定エラー型が発生した場合に処理を分ける
Java版やPython版のように指定したファイルが存在しない場合は無視して処理を続けるためにどう書くのがいいのか分からなかったので、ファイルの存在チェックで処理を分けるようにしてみた。
今回のような場合ならむしろこの書き方のほうがよさそうにも思うけど、Javaなどでよくある
try-catch
的な事をするには、Result
をパターンマッチで特定のエラーの型なら処理を分ける、またはpanic::recover()
を上手く使えばいいんだろうか。String
とstr
とreference
String::as_ref(&args[1])
している箇所の型合わせに苦戦して、最終的にこれで通るようになったが、イマイチこれでいいかどうか分かっていない…。
phone_type.trim()
では&str
同士をパターンマッチしているのだが、ここと&args[1]
とは型が違うという事だろうか。args[1]
, &&args[1]
, args[1].to_string()
など手探りで試してみたがうまくいかず。多分まだ
std::string::String
とstr
とreference
の違いがよく理解できていないと思う…。Rust版・Python版を相互に動かす
Python版のadd_person
で書き込んだファイルをRust版list_people
で読み込めるか、またその逆ができるかを確認してみる。
> cargo run list_people address_py.data Compiling protobuf v1.0.24 Compiling rust-protobuf-example v0.1.0 (file:///home/zaneli/ws/rust-protobuf-example) Finished debug [unoptimized + debuginfo] target(s) in 20.43 secs Running `target/debug/rust-protobuf-example list_people address_py.data` Person ID: 1 Name: ザネリ E-mail address: python@zaneli.com Mobile phone #: 090-1111-2222 Work phone #: 090-3333-4444 Person ID: 2 Name: ざねり
> cargo run add_person address_rust.data Finished debug [unoptimized + debuginfo] target(s) in 0.0 secs Running `target/debug/rust-protobuf-example add_person address_rust.data` Enter person ID: 3 Enter neme: zaneli Enter email address (blank for none): rust@zaneli.com Enter a phone number (or leave blank to finish): 012-3456-7890 Is this a mobile, home, or work phone? home Enter a phone number (or leave blank to finish): 123-4567-8901 Is this a mobile, home, or work phone? Unknown phone type. Using default. Enter a phone number (or leave blank to finish):
> ./list_people_python address_rust.data Person ID: 3 Name: zaneli E-mail address: rust@zaneli.com Home phone #: 012-3456-7890 Mobile phone #: 123-4567-8901よさそう。
明日はtermoshttさんの「データ並列ライブラリRayonを使ってみた」です。