2023年5月に開催されたMotoko Bootcamp Day 2のプロジェクトをRust言語で実装します。
Rustのプロジェクト「day2」を作成します。cargo new
コマンドを--lib
オプションを付与して実行します。
$ cargo new day2 --lib
$ cd day2
生成されたファイルは以下の通りです。
day2
├── Cargo.toml
└── src
└── lib.rs
ic-cdkライブラリを使用します。最新バージョンでよいかと思いますので、以下のように実行ます。
$ cargo add candid ic-cdk serde
candid、serdeは、CandidType, Deserialize指定に必要?
Canister上から関数が正しく呼び出させるようcrate-typeをcdylib
にします。
[lib]
crate-type = ["cdylib"]
Canisterの定義を行います。
{
"canisters": {
"day2": {
"candid": "./day2.did",
"package": "day2",
"type": "rust"
}
},
"defaults": {
"build": {
"args": "",
"packtool": ""
}
},
"version": 1
}
dfx.jsonの [canisters] > [day2] > [candid]項目に指定したファイルに、Canisterに配置するDappが提供する関数のI/Fを定義します。
Motoko Bootcamp Day2 📺 Interfaceに相当するcandidを用意します。
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;
}
MotokoのTimeはint (System time is represent as nanoseconds since 1970-01-01.)のようで、Rust言語ではint128でOKと思われます。
Local caniterに配備してCandid UIで結果を見ると、motokoのResult.Resultはvariant {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と完全一致とはなりません。
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
cargo new
コマンドで生成されたlib.rsの中身をクリアして、day2用のプログラムを作成します。
Motoko Bootcamp Day 2と同じように、以下の関数を実装します。
- addHomework()
- getHomework()
- updateHomework()
- markAsCompleted()
- deleteHomework()
- getAllHomework()
- getPendingHomework()
- searchHomework()
Rust言語仕様の理解が十分でないため、作成したソースコードは所有権まわりをはじめ最適化されていない可能性がありますのでご注意ください。もしも、おかしな実装等が見つかりましたらが、ご指摘いただけますとさいわいです。
Rustではソース内にUnitテストコードを含めて記述することができます。
TODO: 今回の範囲ではロジックにIC色は無いため、UnitテストはLocal canisterに配備せずそのまま実行する方法としましたが、Canisterに配置したテストの方法は未調査。
$ cargo test
Local Canisterを起動します。
--background
オプションでサービス常駐でき、--clean
を付与すると真っ新な状態でLocal canisterを起動できます。
$ dfx start --background --clean
$ dfx deploy