Basic dependency

One common approach to dapp design is to calculate or store data in one canister smart contract – or canister for short – that you can then use in another canister. This ability to share and use functions defined in different canister smart contracts, even if the underlying smart contracts are written in different languages, is an important strategy for building dapps to run the Internet Computer. This tutorial provides a basic introduction to how you can write functions in one language—in the example, Motoko—then use the data in another—in this case, Rust.

For this tutorial, both canister smart contracts are in the same project.

  • The Motoko canister creates an actor with a cell variable to contain the current value that results from an operation.

  • The mul function takes a natural number as input, multiplies the input value by three and stores the result in the cell variable.

  • The Rust canister provides a read function that is a simple query that returns the current value of the cell variable.

Before you begin

Before you start your project, verify the following:

  • You have an internet connection and access to a shell terminal on your local macOS or Linux computer.

  • You have downloaded and installed the Rust programming language and Cargo as described in the Rust installation instructions for your operating system.

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

    The Rust tool chain must be at version 1.46.0, or later.

  • You have downloaded and installed the DFINITY Canister Software Development Kit (SDK) package as described in Download and install.

  • You have cmake installed. For example, use Homebrew with the following command:

    brew install cmake

    For instructions on how to install Homebrew, see the Homebrew Documentation.

  • You have stopped any local canister execution environment processes running on your computer.

If you aren’t sure how to open a new terminal shell on your local computer, run commands in a terminal, or how to check for and install packages, see Preliminary steps for newcomers. If you are comfortable meeting the prerequisites without instructions, continue to Create a new project.

This tutorial takes approximately 20 minutes to complete.

Create a new project

To create a new project directory for this tutorial:

  1. Open a terminal shell on your local computer, if you don’t already have one open.

  2. Create a new project by running the following command:

    dfx new multiply_deps
  3. Change to your project directory by running the following command:

    cd multiply_deps

Modify the default configuration

creating a new project adds several template files to your project directory much like when you create a new Rust package using the cargo new command. You need to modify these default files and add some Rust-specific files to your project before you can build canisters that run on the Internet Computer.

To modify the default configuration for a Motoko project, you’ll need to complete the following steps:

Edit the default canister settings

Because this sample project is going to consist of two canisters-the Motoko canister and the Rust canister—you need to modify the default dfx.json configuration file to include information for building both the Motoko canister and a Rust canister.

To modify the dfx.json configuration file:

  1. Check that you are still in the root directory for your project, if needed.

  2. Open the dfx.json configuration file in a text editor.

  3. Insert a new section after the canisters.multiply_deps settings with settings for building a Rust program using the cargo build command.

    For example, in the canisters section, add a new rust_deps key with settings like these:

    "rust_deps": {
      "build": "cargo build --target wasm32-unknown-unknown --package  rust_deps --release",
      "candid": "src/rust_deps/src/deps.did",
      "wasm": "target/wasm32-unknown-unknown/release/rust_deps.wasm",
      "type": "custom"
      }
  4. Copy the dependencies setting from the multiply_deps_assets section and add it to the settings for the Rust canister.

    The dependencies setting enables you to import functions from one canisters for use in another canister. For this tutorial, we want to import a function from the multiply_deps canister—written in Motoko—and use it from the rust_deps canister written in Rust.

  5. Remove all of the multiply_deps_assets configuration settings from the file.

    The sample dapp for this tutorial doesn’t use any front-end assets, so you can remove those settings from the configuration file.

    You can also remove the defaults and dfx version settings.

    For example, your configuration file might look like this after you modify the settings:

    {
      "canisters": {
        "multiply_deps": {
          "main": "src/multiply_deps/main.mo",
          "type": "motoko"
        },
        "rust_deps": {
          "build": "cargo build --target wasm32-unknown-unknown --package  rust_deps --release",
          "candid": "src/rust_deps/deps.did",
          "wasm": "target/wasm32-unknown-unknown/release/rust_deps.wasm",
          "type": "custom",
          "dependencies": [
            "multiply_deps"
          ]
        }
      },
      "defaults": {
        "build": {
          "packtool": ""
        }
      },
      "networks": {
        "local": {
          "bind": "127.0.0.1:8000",
          "type": "ephemeral"
        },
        "ic-pubs": {
          "providers": [
            "https://gw.dfinity.network"
          ],
          "type": "persistent"
        }
      },
      "version": 1
    }
  6. Save your change and close the dfx.json file to continue.

Add a Cargo.toml file to the project

To build the Rust canister using the cargo build command we added to the dfx.json configuration file, we also need to add a Cargo.toml file to the project directory.

To add Cargo.toml settings for the project:

  1. Check that you are still in the root directory for your project, if needed.

  2. Create a new file in the current directory named Cargo.toml.

  3. Open the Cargo.toml in a text editor.

  4. Use the [workspace] key to specify the source file directory for your canister.

    For example:

    [workspace]
    members = [
        "src/rust_deps",
    ]
  5. Save your changes and close the Cargo.toml file to continue.

Add Rust files to the source directory

Creating a new project creates a default src/multiply_deps directory with a template main.mo for our Motoko canister. For this tutorial, we want to add the files for building a Rust canister.

To prepare the source directory with Rust files:

  1. Check that you are in the root directory for your project, if needed.

  2. Create a new cargo package using a library template by running the following command:

    cargo init --lib src/rust_deps

    This command creates a src/rust_deps/src directory with a library (lib.rs) package and a Cargo.toml file in the src/rust_deps directory.

  3. Open the src/rust_deps/Cargo.toml file in a text editor.

    You use this file to configure the details used to build the Rust package.

    At a minimum, you need to configure the following sections with basic information about the package name, the crate type, and the version of the DFINITY Rust CDK libraries to use.

    • [package]

    • [lib]

    • [dependencies]

  4. Delete the existing [dependencies] section and replace it with the following:

    [lib]
    crate-type = ["cdylib"]
    
    [dependencies]
    ic-cdk = "0.3"
    ic-cdk-macros = "0.3"
    When you deploy the dapp later in the tutorial, you might get an error message that the dependency version is wrong. If there is a newer version of the DFINITY Rust CDK, update the dependencies in the src/rust_deps/Cargo.toml file to match the latest version.
  5. Save your changes and close the file to continue.

Replace the default Motoko canister smart contract

The next step is to replace the default source code in the src/multiply_deps/main.mo file with code that implements the mul and read functions.

To modify the default Motoko source code:

  1. Check that you are still in the root directory for your project, if needed.

  2. Open the src/multiply_deps/main.mo file in a text editor and delete the existing content.

  3. Copy and paste the following sample code into the main.mo file:

    actor Multiply {
    
        var cell : Nat = 1;
    
        public func mul(n:Nat) : async Nat { cell *= n*3; cell };
    
        public query func read() : async Nat {
            cell
        };
    }
  4. Save your changes and close the file to continue.

Replace the default Rust canister smart contract

Now that we have the Motoko canister that the Rust canister depends upon, let’s add the Rust canister to the project.

To replace the default Rust canister:

  1. Check that you are still in the root directory for your project, if needed.

  2. Open the template src/rust_deps/src/lib.rs file in a text editor and delete the existing content.

    The next step is to write a Rust program that imports the Motoko canister and implements the read function.

  3. Copy and paste the following sample code into the lib.rs file:

    use ic_cdk_macros::*;
    use ic_cdk::export::candid;
    
    #[import(canister = "multiply_deps")]
    struct CounterCanister;
    
    #[update]
    async fn read() -> candid::Nat {
        CounterCanister::read().await.0
    }
  4. Save your changes and close the src/rust_deps/src/lib.rs file to continue.

Add an interface description file

Candid is an interface description language (IDL) for interacting with canisters running on the Internet Computer. Candid files provide a language-independent description of a canister’s interfaces including the names, parameters, and result formats and data types for each function a canister defines.

By adding Candid files to your project, you can ensure that data is properly converted from its definition in Rust to run safely on the Internet Computer.

To see details about the Candid interface description language syntax, see the Candid Guide or the Candid crate documentation.

To add a Candid file for this tutorial:

  1. Check that you are still in the root directory for your project, if needed.

  2. Create a new file named deps.did in the src/rust_deps/src directory.

  3. Open the src/rust_deps/src/deps.did file in a text editor.

  4. Copy and paste the following service definition for the read function:

    service : {
      "read": () -> (nat) query;
    }
  5. Save your changes and close the deps.did file to continue.

Start the local canister execution environment

Before you can build the project, you need to connect to the local canister execution environment in your development environment or the Internet Computer mainnet.

To start the network locally:

  1. Check that you are still in the root directory for your project, if needed.

  2. Start the local canister execution environment on your computer in the background by running the following command:

    dfx start --clean --background

    Depending on your platform and local security settings, you might see a warning displayed. If you are prompted to allow or deny incoming network connections, click Allow.

Register, build, and deploy your project

After you connect to the local canister execution environment running in your development environment, you can register, build, and deploy your project locally.

To register, build, and deploy:

  1. Check that you are still in root directory for your project directory, if needed.

  2. Register, build, and deploy the canisters specified in the dfx.json file by running the following command:

    dfx deploy

    The dfx deploy command output displays information about each of the operations it performs similar to the following excerpt:

    Creating a wallet canister on the local network.
    The wallet canister on the "local" network for user "pubs_user_id" is "rwlgt-iiaaa-aaaaa-aaaaa-cai"
    Deploying all canisters.
    Creating canisters...
    Creating canister "multiply_deps"...
    "multiply_deps" canister created with canister id: "rrkah-fqaaa-aaaaa-aaaaq-cai"
    Creating canister "rust_deps"...
    "rust_deps" canister created with canister id: "ryjl3-tyaaa-aaaaa-aaaba-cai"
    Building canisters...
    Executing 'cargo build --target wasm32-unknown-unknown --package  rust_deps --release'
       Compiling ic-cdk v0.3
       Compiling ic-cdk-macros v0.3
       Compiling rust_deps v0.1.0 (/Users/pubs/multiply_deps/src/rust_deps)
        Finished release [optimized] target(s) in 2m 09s
    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 multiply_deps, with canister_id rrkah-fqaaa-aaaaa-aaaaq-cai
    Installing code for canister rust_deps, with canister_id ryjl3-tyaaa-aaaaa-aaaba-cai
    Deployed canisters.

Call functions on the deployed canister

After successfully deploying the canister, you can test the canister by invoking the functions it provides.

For this tutorial:

  • Call the mul function to multiply the value of the cell variable by three each time it is called.

  • Call the read function to return the current value of the cell variable.

To test the deployed canister:

  1. Call the read function from the Motoko canister, which reads the current value of the cell variable on the deployed canister:

    dfx canister call multiply_deps read

    The command returns the current value of the cell variable as one:

    (1)
  2. Call the mul function to multiply the input argument by three by running the following command:

    dfx canister call multiply_deps mul '(3)'

    The command returns the new value of the cell variable:

    (9)
  3. Call the read function using the rust_deps canister that imports functions from the multiply_deps canister:

    dfx canister call rust_deps read

    The command returns the current value of the cell variable:

    (9)

Stop the local canister execution environment

After you finish experimenting with your dapp, you can stop the local canister execution environment so that it doesn’t continue running in the background.

To stop the local canister execution environment:

  1. In the terminal that displays network operations, press Control-C to interrupt the local canister execution environment process.

  2. Stop the local canister execution environment by running the following command:

    dfx stop