ぺんぎんらぼ

お笑いとマンガ好きなしょぼしょぼWeb系エンジニアの日記です。たまに絵を描きます。

お笑いとマンガ好きなしょぼしょぼWeb系エンジニアの日記です

RustでToDoアプリ①~標準入力の内容を出力~

RustでToDoアプリ(CLIで入力してjson形式でファイル保存)

何回かに分け、コマンドラインから値を入力しjsonファイルとして保存するToDoアプリケーションを作っていこうと思います。 その中で併せてRust言語の特徴を説明していきます。

第一回のこの記事では、コマンドラインから値を受け取り出力する処理を紹介します。

コード(main.rs)
use std::io;

fn main() {
    println!("▶ 実行したい内容を選択してください");
    println!("▶ 登録:0,編集:1,削除:2");

    let mut input_data = String::new(); // (1)

    io::stdin()
      .read_line(&mut input_data)/* (2) */
      .expect("標準入力の読み込みに失敗しました");// (3)

    let trimmed_input = input_data.trim(); // (4)

    println!("input_data is {}", input_data); // (5)
  }
解説(1)
let mut input_data = String::new();
  • 右辺で空のString型を宣言し左辺にバインドしています。
  • この変数に、コマンドラインから入力された値を設定したいと思います。
  • Rustではコマンドラインなどから受け取ったデータはString型になります。
  • Rustでは変数は全て「immutable(不変)」となります。初期化後に値を書き換えたい場合は "mut" キーワードを使って「mutable(可変)」にする必要があります。
  • input_data変数を可変にしている理由は、この後この変数に標準入力から受け取ったデータを入れるためです。
解説(2)
io::stdin().read_line(&mut input_data)
  • 上記のように書くとコマンドラインから入力した値をinput_data変数に設定することができます。
  • io::stdin()メソッドでStdin型を返却し、次のread_line()メソッドではResult型を返却しています。
  • 以下は実際のread_lineメソッドの実装です。
    #[stable(feature = "rust1", since = "1.0.0")]
    pub fn read_line(&self, buf: &mut String) -> io::Result<usize> {
        self.lock().read_line(buf)
    }
  • 注目してほしいのがread_lineメソッドの2つの引数のうちの「buf:&mut String」の部分です。 呼び出し側は第2引数の変数bufに対して、可変参照のString型を渡さなければなりません(第1引数の&selfはインスタンス自身を指す記述で、今回は説明しません)。
  • 可変変数にするためにはmutキーワード、参照変数にするためには&キーワードを使います
  • したがって呼び出し側は「read_line(&mut input_data)」と、変数input_dataの頭に&mutを付与して可変参照の変数を渡すようにしています。
解説(2)補足
use std::io;
  • ここではソースコード中で使うstd::ioクレートをimportしています。
  • std::ioクレートはRustの標準ライブラリの一つです。標準ライブラリとは一般的な機能を提供するライブラリで、std::ioはその名の通りファイルの読み書きや入出力に関するライブラリが含まれます。
  • Rustの「クレート」はJavaのjarやwarに近いものです。
  • クレートには二種類あり、①バイナリクレートと②ライブラリクレートがあります。バイナリクレートがそれ自体実行可能なクレート。その一方でライブラリクレートは単体で実行できません。「cargo new パッケージ名」で生成されたパッケージの中にmain.rsが含まれますが、これがバイナリクレートとなります。
解説(3)
io::stdin().read_line(&mut input_data).expect("標準入力の読み込みに失敗しました");
  • expectメソッドはいわばJavaの例外処理をtry−catch文ではなく関数的にワンライナーで書いたものです。もう一度stdinクレートのread_lineメソッドを見てみましょう。
    #[stable(feature = "rust1", since = "1.0.0")]
    pub fn read_line(&self, buf: &mut String) -> io::Result<usize> {
        self.lock().read_line(buf)
    }
  • 戻り値の型が「-> io::Result< usize> 」とResult型になっています。
  • RustにはJavaのような例外の機構はありません。代わりに失敗する可能性がある処理はResult型を返却するということになっています。
  • 基本的には、Result型を受け取った後にmatch構文(Javaのcase式に似た構文です)というものを使って結果が正常な場合〜、Errorの場合〜、と処理内容を実装します。以下はサンプルコードです。
// 呼び出し側(エラーハンドリング)
fn main() {
  let res = match return_result(0){
    Ok(val) => val,
    Err(err) => {
      println!("res is {}", err);
      panic!()
    }
  };
}

// メソッド定義(戻り値はResult型)
fn return_result(a: i32) -> Result<i32,String> {
  if a == 0 {
    Ok(a)
  } else {
    Err(String::from("0以外でした"))
  }
}
  • 「呼び出し側(エラーハンドリング)」を見てください。ご覧の通りmatch式を使うと少し冗長な記述になってしまいます。
  • そこで、expectマクロを使うと記述を省略することができます。expectマクロは「エラーが発生した場合にパニックを発生させる&指定したメッセージを出力する」という機能を提供します。
  • 他にもunwrapやis_okなど様々なエラーハンドリング用のマクロが提供されているので用途によって使い分けます。
  • 以下は先のサンプルコードをexpectマクロを使ったものに置き換えたものです。
// 呼び出し側(エラーハンドリング)
fn main() {
    let res = return_result_data(0).expect("エラーが発生しました。0以外でした");
    println!("res is {}", res);
}
解説(4)
    let trimmed_input = input_data.trim();
  • コマンドラインからの入力には最後に改行文字(\nや\r\n)が含まれます。
  • trim()メソッドは文字列の前後から空白文字を削除するので、この改行文字も削除してくれます。
解説(5)
println!("input_data is {}", input_data);
  • {}はRustでは特別な文字となります。
  • 文字列の中で値を挿入するためのプレースホルダーとして使用されます。
let x = 5;
let y = 4;
println!("x: {}, y: {}", x, y); // 「x: 5, y: 4」が出力される

おわり

以上『RustでToDoアプリ①~標準入力の内容を出力~』でした。

次回は『RustでToDoアプリ②~jsonファイルの内容を出力~』となります。

ここまで読んでくださりありがとうございました。