これはRust その2 Advent Calendarの17日目の記事です。
昨日は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.proto
src/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()を上手く使えばいいんだろうか。
Stringstrreference
String::as_ref(&args[1])している箇所の型合わせに苦戦して、最終的にこれで通るようになったが、
イマイチこれでいいかどうか分かっていない…。
phone_type.trim()では&str同士をパターンマッチしているのだが、ここと&args[1]とは型が違うという事だろうか。
args[1], &&args[1], args[1].to_string()など手探りで試してみたがうまくいかず。
多分まだstd::string::Stringstrreferenceの違いがよく理解できていないと思う…。

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を使ってみた」です。

Copyright© 2011-2019 Shunsuke Otani All Right Reserved .