タスク モジュールを作成する

完了

tasks モジュールは、タスクと、それを保存してアクセスする方法を表します。

src ディレクトリに tasks.rs という名前の新しいファイルを作成します。 そのファイルの中に、まず、プログラムで to-do 項目がどのように表示されるかを表す簡単な構造体を定義します。

use chrono::{DateTime, Utc};

#[derive(Debug)]
pub struct Task {
    pub text: String,
    pub created_at: DateTime<Utc>,
}

この構造体には、次の 2 つのフィールドがあります。

  • text には、"pay the bills" のようにタスクの説明が格納されます。
  • created_at には、タスクの作成のタイムスタンプが格納されます。

to-do リストをタスクのベクトル (Vec<Task>) として表すため、status または is_complete フィールドは追加しません。 そのため、タスクが完了したら、ベクトルから削除することができます。

サードパーティー製クレート chrono を使用していることにお気づきでしょうか。 DateTime 構造体に Utc パラメーターを指定しました。 chrono は、Rust で日付と時刻のデータを処理する必要がある場合に使用するのに適したクレートです。 これには、ある時点を表す簡単な API が用意されています。

これを使用しているので、Cargo.toml ファイルで宣言する必要があります。

[dependencies]
chrono = "0.4"
structopt = "0.3"

次の手順は、新しいタスクをインスタンス化するためのメソッドを実装することです。 タスクには、常に現在の日付と時刻のタイムスタンプが付けられます。 次のコードを Task 構造体の後に追加します。

impl Task {
    pub fn new(text: String) -> Task {
        let created_at: DateTime<Utc> = Utc::now();
        Task { text, created_at }
    }
}

このコードには Task::new 関数が定義されています。 この関数に必要なものは、タスクの説明のみです。 Utc::now() メソッドを使用して、現在のタイムスタンプをキャプチャします。

これでタスクの構造体は完成したようです。 さあ、このモジュールの次の項目 "永続化" に取り組みましょう。

to-do リストをタスクのベクトルとして表すため、JSON ファイルを使用してデータを簡単に永続化することができます。 これを実現するには、Rust エコシステムの別の優れたクレート serde_json を使用することをお勧めします。

serde_json を使用したタスクのシリアル化と逆シリアル化

次に進む前に、Rust でのエンコードとデコードに関して推奨される方法を説明しておきます。

構造体と列挙型インスタンスを永続化する必要がある場合は、"シリアル化" について考える必要があります。 そのデータをプログラムに戻す必要があるときは、"逆シリアル化" について話します。

シリアル化と逆シリアル化は、データをバイト ストリームに格納し、情報を失うことなく、後で使用できるようにデータを取得するプロセスです。 これにより、接続を介してこのようなバイトを送信し、ストレージ デバイスのファイルに保存できるようになります。 シリアル化と逆シリアル化の詳細については、この OWASP チートシートを参照してください。

Rust コミュニティでは、Rust データ構造のほとんどのシリアル化と逆シリアル化を効率的かつ汎用的に処理するために serde クレートを推奨しています。 この既存のクレートを使用することで、生産性と慣用性をさらに高めることができます。

Task 型のシリアル化を始めるには、次の 2 つのクレートが必要です。

  • serde。 型から SerializeDeserialize の特性を派生できるようにするベース クレート。
  • serde_json。 これらの特性を、選択したファイル仕様形式である JSON に実装するクレート。

いつものように、最初の手順は、Cargo.toml ファイルの [dependencies] セクションに serde_jsonserde を含めることです。 今回は、いくつかの serde 機能を条件付きでコンパイルする必要があるため、別の表記法を使用して指定します。 ファイルは次のようになります。

[dependencies]
serde_json = "1.0"    # Add serde_json.
structopt = "0.3"

[dependencies.chrono]
features = ["serde"]  # We're also going to need the serde feature for the chrono crate, so we can serialize the DateTime field.
version = "0.4"

[dependencies.serde]  # Add serde in its own section.
features = ["derive"] # We'll need the derive feature.
version = "1.0"

これで、Task 構造体を調整して serde の新しい機能を使用できるようになりました。 tasks.rs ファイルを開き、構造体を次のように変更します。

use chrono::{serde::ts_seconds, DateTime, Local, Utc};
use serde::Deserialize;
use serde::Serialize;

#[derive(Debug, Deserialize, Serialize)]
pub struct Task {
    pub text: String,

    #[serde(with = "ts_seconds")]
    pub created_at: DateTime<Utc>,
}

違いに注意してください。

  • 実装する特性のリストに DeserializeSerialize を追加しました。
  • ts_secondschrono から serde(with = ...) 属性に渡して created_at フィールドに注釈を付けました。これで、chrono から serde に対して、Datetime 型によって 2 つの新しい特性がどのように実装されるかを通知できるようになりました。

Task 型を使用してシリアル化と逆シリアル化の両方を実行できるようになったので、次に進み、ファイル処理関数を実装することができます。

ファイル システムと対話する

プログラムから実行する必要のある 3 つのアクションを確認しましょう。

  • to-do リストに新しいタスクを追加する。
  • そのリストから完了したタスクを削除する。
  • リスト内の現在のすべてのタスクを出力する。

モジュール インターフェイスは、そのリストと同じくらい単純であるべきです。そのため、アクションごとに 1 つずつ、3 つの関数を使用します。

use std::io::Result;
use std::path::PathBuf;

pub fn add_task(journal_path: PathBuf, task: Task) -> Result<()> { ... }

pub fn complete_task(journal_path: PathBuf, task_position: usize) -> Result<()> { ... }

pub fn list_tasks(journal_path: PathBuf) -> Result<()> { ... }

まず、各関数のシグネチャを見てみましょう。 これらのいずれも journal_path: PathBuf 引数が必須であることに注目してください。 これは、いずれも作業を完了するにはファイルのパス (タスクが格納されるファイルのパス) が必要なためです。

  • add_task は、Task 引数も必須です。 この引数に、リストに追加されるタスクを指定します。
  • complete_task は、削除する Task を示す task_position 引数が必須です。 タスクが削除されると、それが完了したことを意味します。
  • list_tasks に追加の情報は必要ありません。 ジャーナル ファイルに現在格納されているすべてのタスクが、整形された形式でユーザーに表示されるだけです。

いずれの関数も、戻り値の型は同じ std::io::Result<()> です。 この形式は、戻り値の型が I/O の結果であることを示します。 この戻り値の型は、物理的なワード内のデータを処理するときに発生する可能性のある、さまざまな望ましくない結果が予想されることを示しています。 Ok バリアントは空のタプル () です。通常、これはデータがまったく関連付けられていない型です。 その唯一の目的は、関数から Ok が返され、エラーが発生しなかったことを通知することです。

次の 3 つのユニットでは、各関数の内容の記述について詳しく説明します。