はじめてのCanister開発 (Rust)
はじめに
Internet Computer(IC)は、Blockchain技術を応用した分散クラウド環境です。
Canisterと呼ばれるSmart Contractから構成されるDapp(Decentralized Application; 分散型アプリケーション)がICの複数ノード上で動作します。
CanisterにはWebAssemblyモジュールとデータが保持されており、データ更新の際は複数ノード間のコンセンサスによってデータの一貫性が保たれます。
Canisterの開発には、Canister Development Kit(CDK)を使用します。プログラミング言語として、IC用に作られたMotoko以外に、Rust、Python(Kybra)、TypeScript(Azle)に対応しており、本記事ではRust言語での開発方法を解説します。
サンプル概要
解説するHelloサンプルは、CDKのdfx new
コマンドを実行すると生成される最も基本的なプロジェクトです。
サンプルは、以下の2つのCanisterから構成されています。
- Backend Canister
- Frontend Canister
Backend Canisterにはパラメータとして<名前>を受け取ったら『Hello, <名前>!』という文字列を返すgreet()
メソッドを実装しています。
また、Frontend CanisterはWebユーザーインタフェースを提供しており、フォームに<名前>を入力して[Submit]
ボタンをクリックすると、Backend Canisterのgreet()
メソッドを呼び出し、その戻り値を画面に表示する処理を実装しています。
開発環境準備
まずは開発環境を整えます。
筆者はWindows PCを使用していますので、Windows Subsystem for Linux (WSL)のUbuntu 24.04 LTS上に開発環境を構築しています。ご使用の環境に応じて適宜読み替えてください。
CDKインストール
以下のコマンドを実行して、dfx
コマンドやそのバージョン管理を行うdfxvm
コマンドなど開発に使用するツールをインストールします。
$ sh -ci "$(curl -fsSL https://internetcomputer.org/install.sh)"
参考サイト
Rustセットアップ
以下のコマンドを実行してRust環境をセットアップします。
$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
WebAssemblyモジュールをビルドするため、wasm32をターゲットに追加しておいてください。
$ rustup target add wasm32-unknown-unknown
ビルド時、cmakeなど一部の開発用ツールがないとエラーになります。お使いの環境にインストールしておいてください。
Ubuntuの場合は以下のコマンドを実行しておくとよいでしょう。
$ sudo apt install -y build-essential
参考サイト
プロジェクト作成
開発環境が整ったらプロジェクトを作成します。
ターミナル上でdfx new <プロジェクト名>
と入力して実行すると、Backend開発に使用するプログラミング言語やFrontendのフレームワークなどについて対話形式で訊かれた後、Helloサンプルが生成されます。
<プロジェクト名>
には作成するプロジェクトの名称を指定します。例えば、プロジェクト名を『hello』にする場合、以下のコマンドを実行してください。
$ dfx new hello
Backendのプログラミング言語の選択
Backend Canisterの開発に使用するプログラミング言語を訊いてきますので、『Rust』を選択してください。
? Select a backend language: ›
Motoko
❯ Rust
TypeScript (Azle)
Python (Kybra)
Frontend frameworkの選択
Frontend Canisterの開発に使用するWebフレームワークを訊いてきますので、お好みに応じて選択してください。デフォルトは『SvelteKit』です。
? Select a frontend framework: ›
❯ SvelteKit
React
Vue
Vanilla JS
No JS template
No frontend canister
Extra featuresの選択
追加する機能があればチェックしてください。本解説では不要です。
? Add extra features (space to select, enter to confirm) ›
⬚ Internet Identity
⬚ Bitcoin (Regtest)
ディレクトリ構成
dfx
コマンドが完了すると、hello(プロジェクト名)のディレクトリが作成されてファイルが生成されます。
hello
├── .git
│ ︙
├── .gitignore
├── Cargo.lock
├── Cargo.toml
├── README.md
├── dfx.json
├── node_modules
│ ︙
├── package-lock.json
├── package.json
├── src
│ ├── hello_backend
│ │ ︙
│ └── hello_frontend
│ ︙
└── tsconfig.json
Local Canister実行環境の起動
開発時はIC上ではなくローカルPC上にデプロイしてデバッグ等を行うとよいでしょう。dfx start
コマンドで、Local Canister実行環境を起動します。--background
オプションはサービスをバックグラウンド起動するものですので指定しておくとよいでしょう。また、--clean
オプションを指定すると前回起動時の状態を破棄します。
$ cd hello
$ dfx start --background --clean
ビルド・デプロイ
Local Canister実行環境が稼働中の状態でdfx deploy
コマンドを実行すると、ビルドが行われてデプロイされます。
$ dfx deploy
Deploying all canisters.
Creating canisters...
Creating canister hello_backend...
︙
Deployed canisters.
URLs:
Frontend canister via browser
hello_frontend:
- http://127.0.0.1:4943/?canisterId=bd3sg-teaaa-aaaaa-qaaba-cai
- http://bd3sg-teaaa-aaaaa-qaaba-cai.localhost:4943/
Backend canister via Candid interface:
hello_backend: http://127.0.0.1:4943/?canisterId=be2us-64aaa-aaaaa-qaabq-cai&id=bkyz2-fmaaa-aaaaa-qaaaq-cai
※ポート番号やCanisterIdや使用する環境、設定等によって変わります
サンプルの実行
dfx deploy
コマンド実行時に表示されるFrontend CanisterのURLをブラウザからアクセスします。
テキストボックスに適当な名前を入れて[Click Me!]ボタンをクリックすると、Backend Canisterが呼び出され、応答文字列『Hello, <名前>!』が表示されます。
ファイルの解説
dfx.json
プロジェクトに関するJSON形式の設定ファイルです。
Backend Canisterがhello_backend
という名称で定義され、Frontend Canisterがhello_frontend
という名称でそれぞれ定義されています。
{
"canisters": {
"hello_backend": {
"candid": "src/hello_backend/hello_backend.did",
"declarations": {
"node_compatibility": true
},
"package": "hello_backend",
"type": "rust"
},
"hello_frontend": {
"dependencies": [
"hello_backend"
],
"source": [
"src/hello_frontend/dist"
],
"type": "assets",
"workspace": "hello_frontend"
}
},
"defaults": {
"build": {
"args": "",
"packtool": ""
}
},
"output_env_file": ".env",
"version": 1
}
Backendディレクトリ構成
src/hello_backend
ディレクトリにBackend Canister用の設定ファイルとソースファイルが出力されます。Rustを選択した場合は、以下の通りです。
hello
├── src
│ └── hello_backend
│ ├── Cargo.toml
│ ├── hello_backend.did
│ └── src
│ └── lib.rs
src/hello_backend/Cargo.toml
Rustのビルド設定ファイルです。パッケージ名『hello_backend』がdfx.jsonのpackage
項目に対応づいています。
crate-type = ["cdylib"]
を指定し、少なくともcandid
、ic-cdk
ライブラリを追加する必要があります。
[package]
name = "hello_backend"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
crate-type = ["cdylib"]
[dependencies]
candid = "0.10"
ic-cdk = "0.13"
ic-cdk-timers = "0.7" # Feel free to remove this dependency if you don't need timers
src/hello_backend/hello_backend.did
Canisterの公開インタフェースを定義します。Backend Canisterではgreet
というqueryメソッドを公開しています。
service : {
"greet": (text) -> (text) query;
}
参考サイト
Candidの記述方法の詳細は、公式ドキュメントが参考になります。
src/hello_backend/src/lib.rs
#[ic_cdk::query]
fn greet(name: String) -> String {
format!("Hello, {}!", name)
}
Frontendディレクトリ構成
src/hello_frontend
ディレクトリにFrontend Canister用の設定ファイルとソースファイルが出力されます。
Frontend frameworkに『SvelteKit』を選択した場合は以下の通りです。
hello
├── src
│ └── hello_frontend
│ ├── .svelte-kit
│ │ ︙
│ ├── package.json
│ ├── src
│ │ ├── app.d.ts
│ │ ├── app.html
│ │ ├── index.scss
│ │ ├── lib
│ │ │ └── canisters.js
│ │ ├── routes
│ │ │ ├── +layout.js
│ │ │ └── +page.svelte
│ │ └── vite-env.d.ts
│ ├── static
│ │ ├── .ic-assets.json5
│ │ ├── favicon.ico
│ │ └── logo2.svg
│ ├── svelte.config.js
│ ├── tsconfig.json
│ └── vite.config.js
src/hello_frontend/svelte.config.js
SvelteKitなどのWebフレームワークではStatic Site Generation(SSG)とServer Side Rendering(SSR)の方式がありますが、Frontend Canisterは基本的にHTTPリクエストで指定されたパスに対してstaticコンテンツを返すWebホスティングでのでSSGを選択する必要があります。
SvelteKitでは、svelte.config.jsでSSGの設定を行います。ビルドファイルはsrc/hello_frontend/dist
ディレクトリに出力される設定となっており、これはdfx.jsonのsource
項目と対応づいています。
import adapter from '@sveltejs/adapter-static';
/** @type {import('@sveltejs/kit').Config} */
const config = {
kit: {
// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
// If your environment is not supported or you settled on a specific environment, switch out the adapter.
// See https://kit.svelte.dev/docs/adapters for more information about adapters.
adapter: adapter({
pages: 'dist',
assets: 'dist',
fallback: undefined,
precompress: false,
strict: true,
}),
},
};
export default config;
参考サイト
src/hello_frontend/src/routes/+page.svelte
SvelteKitはファイルシステムベースのルーティングを採用しており、src/routes
がリクエストパスのルート(/)になります。
また、ディレクトリ内にある+page.svelte
コンポーネントでWebページが定義されています。
$lib/canisters.js
からbackend
をimportして、onSubmit()
関数内でBackend Canisterのgreet()
メソッドを呼び出しています。
<script>
import "../index.scss";
import { backend } from "$lib/canisters";
let greeting = "";
function onSubmit(event) {
const name = event.target.name.value;
backend.greet(name).then((response) => {
greeting = response;
});
return false;
}
</script>
<main>
<img src="/logo2.svg" alt="DFINITY logo" />
<br />
<br />
<form action="#" on:submit|preventDefault={onSubmit}>
<label for="name">Enter your name: </label>
<input id="name" alt="Name" type="text" />
<button type="submit">Click Me!</button>
</form>
<section id="greeting">{greeting}</section>
</main>
src/hello_frontend/src/lib/canisters.js
dfx deploy
コマンドを実行するとsrc/declarations
ディレクトリにCanisterの公開インタフェースに応じたJavaSciprt呼び出し処理が自動生成されますので、それをimportします。
HelloサンプルのSvelteKit版ではcanisters.js
というファイル内で、自動生成されたcreateActor()
という関数を実行して呼び出しできるようにしています。
import { createActor, canisterId } from 'declarations/hello_backend';
import { building } from '$app/environment';
function dummyActor() {
return new Proxy({}, { get() { throw new Error("Canister invoked while building"); } });
}
const buildingOrTesting = building || process.env.NODE_ENV === "test";
export const backend = buildingOrTesting
? dummyActor()
: createActor(canisterId);
Discussion