だいたい死んでる

名古屋で働いているWEBプログラマーです。

プログラミング言語Rust触ってみる

Rustでコマンドラインアプリを作るためのドキュメントがあったので試してみる。

rust-cli.github.io

インストール

インストールしていない方はこちらからどうぞ。

www.rust-lang.org

作っていく

プロジェクトセットアップ

$ cargo new grrs
     Created binary (application) `grrs` package
$ cd grrs/
$ cargo run
   Compiling grrs v0.1.0 (/grrs)
    Finished dev [unoptimized + debuginfo] target(s) in 3.73s
     Running `target/debug/grrs`
Hello, world!

Hello, world!が表示されれば完了

作るCLIアプリについて

引数で与えたテキストをtext.txtに入力するコマンドを作る。

 grrs foobar test.txt

引数を取得できるようにする

第一引数でパターンを取得し、第2引数でパスを与える。

--- a/src/main.rs
+++ b/src/main.rs
+    let pattern = std::env::args().nth(1).expect("no pattern given");
+    let path = std::env::args().nth(2).expect("no path given");

引数をデータ型にする

Rustでは、扱うデータを中心にプログラムを構成するのが非常に一般的らしいので、データ型を定義します。

--- a/src/main.rs
+++ b/src/main.rs
     let pattern = std::env::args().nth(1).expect("no pattern given");
     let path = std::env::args().nth(2).expect("no path given");

+    struct Cli {
+        pattern: String,
+        path: std::path::PathBuf,
+    }
 }

StructOptを使用したCLI引数の解析

コマンドライン引数を解析するためのライブラリにclapがあります。サブコマンド、シェル補完、ヘルプメッセージのサポートがされている。

+++ b/Cargo.toml
 [dependencies]
+structopt = "0.2.10"

structoptライブラリはclap上に構築され、派生マクロを生成するためのstructの定義を提供しています。 構造体に注釈を加えることで、引数をフィールドに解析するコードが生成されます。

--- a/src/main.rs
+++ b/src/main.rs
+use structopt::StructOpt;
+
+/// Search for a pattern in a file and display the lines that contain it.
+#[derive(StructOpt)]
+struct Cli {
+    /// The pattern to look for
+    pattern: String,
+    /// The path to the file to read
+    #[structopt(parse(from_os_str))]
+    path: std::path::PathBuf,
+}

構造体を呼び出すときは以下のコードからできます。

--- a/src/main.rs
+++ b/src/main.rs
 fn main() {
-    println!("Hello, world!");
+    let args = Cli::from_args();
 }

実行してみる

$ cargo run
error: The following required arguments were not provided:
    <pattern>
    <path>

USAGE:
    grrs <pattern> <path>

For more information try --help

引数を与えてみる。

$ cargo run -- some-pattern some-file
    Finished dev [unoptimized + debuginfo] target(s) in 0.02s
     Running `target/debug/grrs some-pattern some-file`

引数を処理する

引数を取得しファイルを習得するコードを追加する。

--- a/src/main.rs
+++ b/src/main.rs
 fn main() {
     let args = Cli::from_args();
+    let content = std::fs::read_to_string(&args.path)
+        .expect("could not read file");
 }

行を反復して印字する処理を追加します。

--- a/src/main.rs
+++ b/src/main.rs
     let args = Cli::from_args();
     let content = std::fs::read_to_string(&args.path)
         .expect("could not read file");
+
+    for line in content.lines() {
+        if line.contains(&args.pattern) {
+            println!("{}", line);
+        }
+    }
 }

実行してみる

$ cargo run -- main src/main.rs
    Finished dev [unoptimized + debuginfo] target(s) in 0.02s
     Running `target/debug/grrs main src/main.rs`
fn main() {

ファイル内から引数で与えた文字を探し出し印字している。

エラーハンドリングを追加する

エラーを戻り値として処理できるようにするライブラリを追加する。

--- a/Cargo.toml
+++ b/Cargo.toml
 [dependencies]
 structopt = "0.2.10"
+failure = "0.1.5"
+exitfailure = "0.5.1"

ファイルが読めなかった場合のエラーと正常な場合の出力を記述する。

--- a/src/main.rs
+++ b/src/main.rs

+use failure::ResultExt;
+use exitfailure::ExitFailure;

 /// Search for a pattern in a file and display the lines that contain it.
 #[derive(StructOpt)]
@@ -10,14 +12,14 @@ struct Cli {
     path: std::path::PathBuf,
 }

-fn main() {
+fn main() -> Result<(), ExitFailure>  {
     let args = Cli::from_args();
     let content = std::fs::read_to_string(&args.path)
-        .expect("could not read file");
-
-    for line in content.lines() {
+            .with_context(|_| format!("could not read file `{}`", args.path.display()))?;
+
+    Ok(for line in content.lines() {
         if line.contains(&args.pattern) {
             println!("{}", line);
         }
-    }
+    })
 }

実行してみる

# ファイルが有る場合
    Finished dev [unoptimized + debuginfo] target(s) in 0.03s
     Running `target/debug/grrs main src/main.rs`
fn main() -> Result<(), ExitFailure>  {

# ファイルがない場合
$ cargo  run -- main src/main.r
    Finished dev [unoptimized + debuginfo] target(s) in 0.03s
     Running `target/debug/grrs main src/main.r`
Error: could not read file `src/main.r`
Info: caused by No such file or directory (os error 2)

まとめ

Rustを初めて触りましたがドキュメントが充実していた。また、サンプルプログラムの説明も詳しく、初学者に優しい印象を感じました。 今回はテストを省いているので、テストをちゃんと書けるようになっていきたいです。