Charaken 技術系ブログ

技術系に関して学んだことや、気になったことを記事にしていく。

【Rust】がばがばRust独学 - 12. I/O Project

f:id:charaken:20191223210559p:plain

Rustの公式ドキュメントを通して、Rustを独学していく記事になります。(がば要素あり)

doc.rust-lang.org

今回は、I/Oについて学びつつ、気になった点をコード確認した結果を記事にしています。

  • versions
$ rustc --version
rustc 1.40.0 (73528e339 2019-12-16)


Accepting Command Line Arguments

An I/O Project: Building a Command Line Program - The Rust Programming Language

引数読み取り

env::args().collect() により、引数の読み取りを実施できます。

use std::env;

fn main() {
    let args: Vec<String> = env::args().collect();
    println!("{:?}", args);
}
bash-3.2$ cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.04s
     Running `target/debug/minigrep`
["target/debug/minigrep"]
bash-3.2$ cargo run needle haystack
    Finished dev [unoptimized + debuginfo] target(s) in 0.00s
     Running `target/debug/minigrep needle haystack`
["target/debug/minigrep", "needle", "haystack"]

引数の格納

&args[x] により引数を格納することが可能です。

use std::env;

fn main() {
    let args: Vec<String> = env::args().collect();
    println!("{:?}", args);

    let query = &args[1];
    let filename = &args[2];

    println!("Searching for {}", query);
    println!("In file {}", filename);
}
$ cargo run searchstring example-filename.txt
["target/debug/minigrep", "searchstring", "example-filename.txt"]
Searching for searchstring
In file example-filename.txt

もちろん、直でindexを指定しているため、引数が足りていない場合はpanicを引き起こします。

bash-3.2$ cargo run 
   Compiling minigrep v0.1.0 (/Users/k-hara/Work/training-rust/minigrep)
    Finished dev [unoptimized + debuginfo] target(s) in 0.29s
     Running `target/debug/minigrep`
["target/debug/minigrep"]
thread 'main' panicked at 'index out of bounds: the len is 1 but the index is 1', /rustc/73528e339aae0f17a15ffa49a8ac608f50c6cf14/src/libcore/slice/mod.rs:2796:10
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.

Reading a File

Reading a File - The Rust Programming Language

demoコードと同様に poem.txt を作成し、下記のように fs::read_to_string を利用することでファイルを取得可能です。

use std::env;
use std::fs;

fn main() {
    //
    // Accepting Command Line Arguments
    //
    let args: Vec<String> = env::args().collect();
    println!("{:?}", args);

    let query = &args[1];
    let filename = &args[2];

    println!("Searching for {}", query);
    println!("In file {}", filename);

    //
    // Reading a File
    //
    let contents = fs::read_to_string(filename).expect("Something went wrong reading the file");

    println!("With text:\n{}", contents);
}
bash-3.2$ cargo run the poem.txt
   Compiling minigrep v0.1.0 (/Users/k-hara/Work/training-rust/minigrep)
    Finished dev [unoptimized + debuginfo] target(s) in 0.33s
     Running `target/debug/minigrep the poem.txt`
["target/debug/minigrep", "the", "poem.txt"]
Searching for the
In file poem.txt
With text:
I'm nobody! Who are you?
Are you nobody, too?
Then there's a pair of us - don't tell!
They'd banish us, you know.

How dreary to be somebody!
How public, like a frog
To tell your name the livelong day
To an admiring bog!

Refactoring to Improve Modularity and Error Handling

Refactoring to Improve Modularity and Error Handling - The Rust Programming Language

公式ドキュメントでは、作成したプログラムに対して4つの問題点を提示しています。

  1. main 関数による2つのタスク(コマンドラインの読み込み、ファイルの読み取り)を実行
    • main の肥大化への懸念
  2. queryfilenamecontents のスコープ
    • スコープ内の変数が多いほど、変数追跡が難しくなることへの懸念
  3. expect のメッセージ
    • 失敗の可能性(ファイルがない場合や権限がない場合)などが分からず、UXが悪い
  4. コマンドライン引数がない場合の処理
    • index out of bounds しか出力されないため、UXが悪い

Separation of Concerns for Binary Projects

問題点を解決するために、 main.rslib.rs に綺麗に分割した方が良いです。

公式ドキュメントより、 main.rs の責務を掻い摘むと、下記のようになります。

  • ロジックは lib.rs に分割して、呼び出しを main.rs の責務とする
  • エラーが含まれるロジックに対してのエラー処理を main.rs の責務とする

Developing the Library’s Functionality with Test-Driven Development

Developing the Library’s Functionality with Test Driven Development - The Rust Programming Language

search 関数をTDDを利用して作成するということでした。

記載されている手順をまとめると、下記の流れでした。

  1. 失敗するテスト作成・実行し、予測通りになっているか確認
  2. テストに合格する十分なコードを作成
  3. 作成したコードをリファクタリングして、テストをパスか確認
  4. 手順1から繰り返す。

Working with Environment Variables

Working with Environment Variables - The Rust Programming Language

env::var("CASE_INSENSITIVE").is_err() により環境変数を利用可能とする。

デモコードでは、CASE_INSENSITIVE により、大文字・小文字を識別するかどうかを変更している。


Writing Error Messages to Standard Error Instead of Standard Output

Writing Error Messages to Standard Error Instead of Standard Output - The Rust Programming Language

eprintln! を利用することで、標準エラー出力となる。


まとめ

github.com

  • env::args().collect() によりコマンドライン引数を利用可能
    • use std::env; による env の呼び出しが必須
  • fs::read_to_string によりファイル呼び出しが利用可能
    • use std::fs; による env の呼び出しが必須
  • メンテナンスのために、 main.rslib.rs に綺麗に分割が必要
  • TDDで開発しよう
  • env::var("XXXXX").is_err() により環境変数を利用可能
  • eprintln!標準エラー出力

最後に

I/O を絡めた開発手法も含めて、エラーの引き回し方など基本的なプロジェクトの作成方法がおさえられる、良いドキュメントでした。

プロジェクト開発する前に読んでおくべきですね・・・

次は functional language featusです。