Homework diary 📔

2023年5月に開催されたMotoko Bootcamp Day 2のプロジェクトをRust言語で実装します。

1. Rustプロジェクト作成

Rustのプロジェクト「day2」を作成します。cargo newコマンドを--libオプションを付与して実行します。

$ cargo new day2 --lib
$ cd day2

生成されたファイルは以下の通りです。

day2
├── Cargo.toml
└── src
    └── lib.rs

2. Cargo.tomlの編集

(1) IC関連ライブラリ追加

ic-cdkライブラリを使用します。最新バージョンでよいかと思いますので、以下のように実行ます。

$ cargo add candid ic-cdk serde

candid、serdeは、CandidType, Deserialize指定に必要?

(2) crate-type設定

Canister上から関数が正しく呼び出させるようcrate-typeをcdylibにします。

[lib]
crate-type = ["cdylib"]

Cargo.toml

3. dfx.jsonの作成

Canisterの定義を行います。

dfx.json

{
  "canisters": {
    "day2": {
      "candid": "./day2.did",
      "package": "day2",
      "type": "rust"
    }
  },
  "defaults": {
    "build": {
      "args": "",
      "packtool": ""
    }
  },
  "version": 1
}

4. candidの作成

dfx.jsonの [canisters] > [day2] > [candid]項目に指定したファイルに、Canisterに配置するDappが提供する関数のI/Fを定義します。

Motoko Bootcamp Day2 📺 Interfaceに相当するcandidを用意します。

day2.did

type Time = int;
type Homework = record {
    "title": text;
    "description": text;
    "dueDate": Time;
    "completed": bool;
};

type ResultHomework = variant { Ok: Homework; Err: text };
type ResultOnly = variant { Ok; Err: text };

service: {
    addHomework: (Homework) -> (nat);
    getHomework: (nat) -> (ResultHomework) query;
    updateHomework: (nat, Homework) -> (ResultOnly);
    markAsCompleted: (nat) -> (ResultOnly);
    deleteHomework: (nat) -> (ResultOnly);
    getAllHomework: () -> (vec Homework) query;
    getPendingHomework: () -> (vec Homework) query;
    searchHomework: (text) -> (vec Homework) query;
}

Time型

MotokoのTimeはint (System time is represent as nanoseconds since 1970-01-01.)のようで、Rust言語ではint128でOKと思われます。

Result型

Local caniterに配備してCandid UIで結果を見ると、motokoのResult.Resultvariant {ok:xxx, err:text}(先頭小文字)となりますが、RustのResult<T, E>variant {Ok:xxx, Err:text} (先頭大文字)の違いがあるようです。

そのため、candid定義を Motoko に合わせて、variant { ok:Homework; err: text }とするとRust側でResultを返す際にマッピングエラーとなってしまい、Motoko Bootcamp Day 2と完全一致とはなりません。

2023/06/23追記:

tokuryoo氏より教えていただいた情報より、MotokoのResultに合わせるためにはRust標準のstd::Resultを使用せず、Motokoに合わせてenum型を用意する必要があるようです。

本ドキュメントではstd::Result型を使用する方法説明しますが、MotokoとI/Fを合わせる場合には、tokuryoo氏の内容が参考になります。

https://github.com/tokuryoo/motokobootcamp-rust-tokuryoo/tree/main/day2/src/day2_backend

5. lib.rsの編集

cargo newコマンドで生成されたlib.rsの中身をクリアして、day2用のプログラムを作成します。

Motoko Bootcamp Day 2と同じように、以下の関数を実装します。

  • addHomework()

  • getHomework()

  • updateHomework()

  • markAsCompleted()

  • deleteHomework()

  • getAllHomework()

  • getPendingHomework()

  • searchHomework()

lib.rs

Rust言語仕様の理解が十分でないため、作成したソースコードは所有権まわりをはじめ最適化されていない可能性がありますのでご注意ください。もしも、おかしな実装等が見つかりましたらが、ご指摘いただけますとさいわいです。

6. Unitテスト

Rustではソース内にUnitテストコードを含めて記述することができます。

TODO: 今回の範囲ではロジックにIC色は無いため、UnitテストはLocal canisterに配備せずそのまま実行する方法としましたが、Canisterに配置したテストの方法は未調査。

$ cargo test

7. Local Canisterの起動

Local Canisterを起動します。

--backgroundオプションでサービス常駐でき、--cleanを付与すると真っ新な状態でLocal canisterを起動できます。

$ dfx start --background --clean

8. Local Canisterへの配備

$ dfx deploy

最終更新