シンプルなレコードの追加と検索

このチュートリアルでは、名前・説明・キーワードの配列で構成される、シンプルなプロフィールレコードを追加・取得するためのいくつかの基本的な関数を有する Dapp を書きます。

このプログラムは、以下の関数を有します:

  • update 関数は、namedescriptionkeywords からなるプロフィールを追加することができます。

  • getSelf 関数は、関数の呼び出し元に関連づけられたプリンシパルのプロフィールを返します。

  • get 関数は、渡された name の値にマッチするプロフィールを返す単純なクエリ関数です。 この関数では、指定された名前が name フィールドと完全に一致する必要があります。

  • search 関数は、より複雑なクエリを実行して、任意のフィールドで指定されたテキストのすべてまたは一部に一致するプロフィールを返します。例えば、特定のキーワードを含むプロフィールや、名前や説明の一部のみにマッチするプロフィールを返すことができます。

このチュートリアルでは、Rust CDK のインターフェースとマクロを使用して、Rust で Internet Computer 用の Dapps を簡単に書けるようにするための簡単な例を紹介しています。

このチュートリアルでは以下を説明します:

  • Candid インターフェース記述言語を使った、少し複雑なデータ(record とキーワードの array)の表現の仕方。

  • 部分一致する文字列を照合するシンプルな検索機能の書き方。

  • 特定のプリンシパルとプロフィールの関連付け方。

はじめる前に

プロジェクトをはじめる前に、以下をご確認ください:

  • インターネットに接続しており、ローカルの macOS または Linux コンピュータでターミナルにアクセスできること。

  • Rust のインストール方法 にあるように、Rust プログラミング言語と Cargo が OS にダウンロードされ、インストールされていること。

    curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh

    Rust のバージョンは 1.46.0 より新しい必要があります。

  • ダウンロードとインストールの説明に従って、DFINITY Canister Software Development Kit (SDK) パッケージのダウンロードとインストールが済んでいること。

  • cmake のインストールが済んでいること。例えば、macOS では Homebrew を使って以下のコマンドを実行します:

    brew install cmake

    Homebrew をインストールする方法については、Homebrew のドキュメントを参照してください。

  • コンピュータ上の ローカル実行環境 プロセスが停止していること。

ローカルコンピュータ上で新たなターミナルを開く方法や、ターミナルでコマンドを実行する方法や、インストールパッケージを確認する方法がわからない場合には、初めての人のための準備を参照してください。 既に必要な前提条件を満たしている場合には、新しいプロジェクトの作成 へと進んでください。

このチュートリアルは、完走に約 20 分かかります。

新しいプロジェクトの作成

新しいプロジェクトを作る手順は以下になります:

  1. ローカル PC でターミナルを開きます。

  2. 以下のコマンドを実行して新しいプロジェクトを作成します。

    dfx new --type=rust rust_profile
  3. 以下のコマンドで、プロジェクトディレクトリに移動します。

    cd rust_profile

デフォルトプロジェクトの変更

Hello World! Rust CDK クイックスタートでは、Rust Canister のデフォルトプロジェクトのファイルを確認しました。

このチュートリアルを完了するには、以下の手順を踏みます。

デフォルト Dapp の置き換え

Rust Dapp 用のファイルが揃ったので、テンプレートの lib.rs Dapp を Internet Computer にデプロイしたい Rust Dapp に置き換えていきます。

デフォルトのプログラムを置き換えるには、以下のようにします。

  1. 自分がプロジェクトのルートディレクトリにいることを確認します。

  2. テキストエディタで src/rust_profile/Cargo.toml ファイルを開き、依存関係に serde を追加します。

    [dependencies]
    ic-cdk = "0.3"
    ic-cdk-macros = "0.3"
    serde = "1.0"
  3. src/rust_profile/lib.rs ファイルをテキストエディタで開き、既存の内容を削除します。

    次に、getSelfupdategetsearch 関数を Rust プログラムで実装していきます。

  4. 下のサンプルコードを profile.rs にコピー&ペーストしてください。

    use ic_cdk::{
        call::{self, ManualReply},
        export::{
            candid::{CandidType, Deserialize},
            Principal,
        },
    };
    use ic_cdk_macros::*;
    use std::cell::RefCell;
    use std::collections::BTreeMap;
    
    type IdStore = BTreeMap<String, Principal>;
    type ProfileStore = BTreeMap<Principal, Profile>;
    
    #[derive(Clone, Debug, Default, CandidType, Deserialize)]
    struct Profile {
        pub name: String,
        pub description: String,
        pub keywords: Vec<String>,
    }
    
    thread_local! {
        static PROFILE_STORE: RefCell<ProfileStore> = RefCell::default();
        static ID_STORE: RefCell<IdStore> = RefCell::default();
    }
    
    #[query(name = "getSelf")]
    fn get_self() -> Profile {
        let id = ic_cdk::api::caller();
        PROFILE_STORE.with(|profile_store| {
            profile_store
                .borrow()
                .get(&id)
                .cloned()
                .unwrap_or_else(|| Profile::default())
        })
    }
    
    #[query]
    fn get(name: String) -> Profile {
        ID_STORE.with(|id_store| {
            PROFILE_STORE.with(|profile_store| {
                id_store
                    .borrow()
                    .get(&name)
                    .and_then(|id| profile_store.borrow().get(id).cloned())
                    .unwrap_or_else(|| Profile::default())
            })
        })
    }
    
    #[update]
    fn update(profile: Profile) {
        let principal_id = ic_cdk::api::caller();
        ID_STORE.with(|id_store| {
            id_store
                .borrow_mut()
                .insert(profile.name.clone(), principal_id);
        });
        PROFILE_STORE.with(|profile_store| {
            profile_store.borrow_mut().insert(principal_id, profile);
        });
    }
    
    #[query(manual_reply = true)]
    fn search(text: String) -> ManualReply<Option<Profile>> {
        let text = text.to_lowercase();
        PROFILE_STORE.with(|profile_store| {
            for (_, p) in profile_store.borrow().iter() {
                if p.name.to_lowercase().contains(&text) || p.description.to_lowercase().contains(&text)
                {
                    return ManualReply::one(Some(p));
                }
    
                for x in p.keywords.iter() {
                    if x.to_lowercase() == text {
                        return ManualReply::one(Some(p));
                    }
                }
            }
        });
    
        ManualReply::one(None::<Profile>)
    }
  5. 変更を保存してファイルを閉じ、次に進みます。

インターフェイス記述ファイルの更新

Candid は、Internet Computer で動作する Canister と対話するためのインターフェース記述言語(IDL)です。 Candid ファイルは、Canister が定義する各関数の名前・引数・返し値のフォーマットやデータ型など、Canister のインターフェースを言語に依存しないように記述したものです。

Candid ファイルをプロジェクトに追加することで、Rust で定義されたデータが Internet Computer 上で安全に実行されるために適切に変換されることを保証します。

Candid インターフェース記述言語の構文の詳細は Candid ガイドCandid クレートのドキュメントをご覧ください。

Candid ファイルを更新するには、以下のようにします:

  1. 自分がプロジェクトのルートディレクトリにいることを確認します。

  2. src/rust_profile/rust_profile.did ファイルをテキストエディタで開いてください。

  3. getSelfupdategetsearch 関数のために以下の service の定義をコピー&ペーストして書き込んでください:

    type Profile_2 = record {
        "name": text;
        "description": text;
        "keywords": vec text;
    };
    type Profile = Profile_2;
    
    service : {
        "getSelf": () -> (Profile_2) query;
        "get": (text) -> (Profile_2) query;
        "update": (Profile_2) -> ();
        "search": (text) -> (opt Profile_2) query;
    }
  4. 変更を保存してファイルを閉じ、次に進んでください。

ローカル実行環境 を立ち上げる

rust_profile プロジェクトをビルドする前に、ローカル実行環境 か、Internet Computer メインネットに接続する必要があります。

ローカル実行環境 を立ち上げるには、以下のようにします:

  1. 自分がプロジェクトのルートディレクトリにいることを確認します。

  2. ローカル実行環境 をバックグラウンドで立ち上げるために、以下のコマンドを実行します:

    dfx start --background --clean

    プラットフォームやローカルのセキュリティ設定によっては、警告が表示される場合があります。 ネットワーク接続を許可するかどうかの確認画面が表示された場合は、Allow をクリックします。

プロジェクトの登録・ビルド・デプロイ

開発環境で立ち上がっている ローカル実行環境 に接続した後に、プロジェクトの登録・ビルド・デプロイをローカル環境で行うことができます。

登録・ビルド・デプロイを行うためには、以下のようにします:

  1. プロジェクトのルートディレクトリにいることを確認します。

  2. dfx.json に指定されている Canister を以下のコマンドで、登録・ビルド・デプロイします:

    dfx deploy

    dfx deploy コマンドを実行すると、以下のような実行結果に関しての情報が表示されます。

    Creating a wallet canister on the local network.
    The wallet canister on the "local" network for user "default" is "rwlgt-iiaaa-aaaaa-aaaaa-cai"
    Deploying all canisters.
    Creating canisters...
    Creating canister "rust_profile"...
    "rust_profile" canister created with canister id: "rrkah-fqaaa-aaaaa-aaaaq-cai"
    Creating canister "rust_profile_assets"...
    "rust_profile_assets" canister created with canister id: "ryjl3-tyaaa-aaaaa-aaaba-cai"
    Building canisters...
    Executing: "cargo" "build" "--target" "wasm32-unknown-unknown" "--release" "-p" "rust_profile"
    ...
        Finished release [optimized] target(s) in 6.31s
    Building frontend...
    Installing canisters...
    Creating UI canister on the local network.
    The UI canister on the "local" network is "r7inp-6aaaa-aaaaa-aaabq-cai"
    Installing code for canister rust_profile, with canister_id rrkah-fqaaa-aaaaa-aaaaq-cai
    ...
    Deployed canisters.

デプロイした Canister の関数を呼ぶ

Canister のデプロイが成功すると、Canister の関数を呼ぶことでテストできるようになります。

このチュートリアルでは、以下のようにします:

  • update 関数を呼び、プロフィールを追加する。

  • getSelf 関数を呼び、プリンシパル ID のプロフィールを表示する。

  • search 関数を呼び、キーワードを使ってプロフィールを検索する。

デプロイした Canister をテストするために:

  1. update 関数を呼び、プロフィールレコードを作成するには以下のコマンドを実行します:

    dfx canister call rust_profile update '(record {name = "Luxi"; description = "mountain dog"; keywords = vec {"scars"; "toast"}})'
  2. getSelf 関数を呼び、プロフィールレコードを取得するには以下のコマンドを実行します:

    dfx canister call rust_profile getSelf

    このコマンドは、update 関数を使って追加したプロフィールを返します。 例えば、以下のようになります:

    (  record {
        name = "Luxi";
        description = "mountain dog";
        keywords = vec { "scars"; "toast" };
      },
    )

    いまのところ、この Dapp は1つのプロフィールを保存して返すだけです。 次のコマンドを実行して、update 関数を使って2つ目のプロフィールを追加すると、Luxi のプロフィールが Dupree のプロフィールに置き換わります:

    dfx canister call rust_profile update '(record {name = "Dupree"; description = "black dog"; keywords = vec {"funny tail"; "white nose"}})'

    getgetSelfsearch 関数を使っても、Dupree のプロフィールが返ってくるだけです。

  3. search 関数を以下のように実行してみましょう。

    dfx canister call rust_profile search '("black")';

    このコマンドは description との一致を見つけ、以下のプロフィールを返します:

    (
      opt record {
        name = "Dupree";
        description = "black dog";
        keywords = vec { "funny tail"; "white nose" };
      },

新しい ID に対するプロフィールの追加

いまのところ、この Dapp はコマンドを実行したプリンシパルに関連付けられた1つのプロフィールしか保存しません。 関数 get, getSelf, search が思った通りに動作することをテストするために、異なるプロフィールを持つことができる新しい ID を追加する必要があります。

テストのために ID を追加します:

  1. 以下のコマンドで新しいユーザー ID を作成します。

    dfx identity new Miles
    Creating identity: "Miles".
    Created identity: "Miles".
  2. update 関数を呼び、新しい ID に対応したプロフィールを追加します。

    dfx --identity Miles canister call rust_profile update '(record {name = "Miles"; description = "Great Dane"; keywords = vec {"Boston"; "mantle"; "three-legged"}})'
  3. getSelf 関数を呼び、default のユーザー ID に紐づけられたプロフィールを確認します。

    dfx canister call rust_profile getSelf

    このコマンドはデフォルトの ID に現在紐づけられているプロフィールを表示ます。この例では、Dupree のプロフィールです:

    (
      record {
        name = "Dupree";
        description = "black dog";
        keywords = vec { "funny tail"; "white nose" };
      },
    )
  4. Miles のユーザー ID を使って getSelf 関数を呼ぶには以下のコマンドを実行します:

    dfx --identity Miles canister call rust_profile getSelf

    このコマンドは Miles の ID に現在紐づいているプロフィールを表示します。この例では、以下のようになります:

    (
      record {
        name = "Miles";
        description = "Great Dane";
        keywords = vec { "Boston"; "mantle"; "three-legged" };
      },
    )
  5. 正しいプロフィールが返されるかどうかをテストするために、説明の一部やキーワードを使って search 関数を呼び出しましょう。

    例えば、Miles のプロフィールが返されることを検証するために、以下のコマンドを実行します:

    dfx canister call rust_profile search '("Great")'

    このコマンドは Miles のプロフィールを返します:

    (
      opt record {
        name = "Miles";
        description = "Great Dane";
        keywords = vec { "Boston"; "mantle"; "three-legged" };
      },
    )
  6. 正しいプロフィールが返されるかどうかをさらにテストするために、search 関数を呼びます。

    例えば、Dupree のプロフィールが返されることを検証するために、以下のコマンドを実行します:

    dfx canister call rust_profile search '("black")'

    このコマンドは Dupree のプロフィールを返します:

    (
      opt record {
        name = "Dupree";
        description = "black dog";
        keywords = vec { "funny tail"; "white nose" };
      },
    )

サンプル Dapp の拡張

このサンプル Dapp は、ユーザー ID ごとに1つのプロフィールを保存するだけです。 もし各ユーザーのプロフィールにソーシャルコネクションへのリンクを追加するような拡張を行えば、LinkedUp サンプルアプリケーションを Rust で再現することができるでしょう。

ローカル実行環境 を止める

アプリケーションのテストをした後は、ローカル実行環境 がバックグラウンドで稼働し続けないように、以下の手順で停止します:

  1. ネットワークの稼働状況が表示されている端末で、Control-C を押して ローカル実行環境 のプロセスを止めてください。

  2. 以下のコマンドを用いて ローカル実行環境 を停止してください:

    dfx stop