Basic dependency

One common approach to application design is to calculate or store data in one program that you can then use in another program. This ability to share and use functions defined in different application canisters, even if the underlying programs are written in different languages, is an important strategy for building applications 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 programs are in the same project.

  • The Motoko program 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 lang program provides a read function that is a simple query that returns the current value of the cell variable.

Before you begin

Before starting the tutorial, verify the following:

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

  • You have downloaded and installed the DFINITY Canister SDK package as described in Download and install.

  • You have downloaded the latest version of the DFINITY Canister Development Kit (CDK) for Rust from the Rust community’s crate registry or from the Rust CDK repository.

  • You have stopped any Internet Computer network processes running on the local computer.

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. Change to the folder you are using for your Internet Computer sample projects.

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

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

    cd multiply_deps

Modify the default configuration

Because this sample project is going to consist of two programs-the Motoko program and the Rust program—you need to modify the default dfx.json configuration file include information for a Rust program. To build the Rust program using the command you specify in the dfx.json configuration file, you also need to add a Cargo.toml file to the project directory.

Edit canister settings

To modify the default dfx.json configuration file to include the Rust program:

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

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

    For example, 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/deps.did",
      "wasm": "target/wasm32-unknown-unknown/release/rust_deps.wasm",
      "type": "custom"
      }
  3. Copy the dependencies setting from the multiply_deps_assets section and add it to the settings for the Rust program.

    The dependencies setting enables you to import function from the specified program, in this case the multiply_deps program written in Motoko.

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

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

    Optionally, you can also remove the dfx version and remove or modify the network 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
    }
  5. Save your change and close the dfx.json file to continue.

Add Cargo.toml settings to the project

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 named Cargo.toml.

  3. Use the [workspace] key to specify the source file directories for your program.

    For example:

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

Modify the default program

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

To modify the default template 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.

Add the Rust program

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

This step involves updating the source code directory and adding some required files. For example, before you build this projects to be deployed on the Internet Computer, you need to do the following:

  • Add the main.rs that contain the Rust program.

  • Add a second Cargo.toml file for building the program.

  • Add a Candid interface description file to describe the type signatures for the program.

Add a directory for the Rust program

  1. Change to the src source code directory for the project by running the following command:

    cd src

    If you want to keep your workspace tidy, you might want to remove the multiply_deps_assets directory for front-end assets by running the rm -rf multiply_deps_assets command.

  2. Add a directory for the Rust program by running the following command:

    mkdir rust_deps
  3. Change to the source code directory for your program.

    For example:

    cd rust_deps
  4. Add a new main.rs Rust program file to import the Motoko program functions.

  5. Open the main.rs file in a text editor, then copy and paste the following sample code into the main.rs file:

    use ic_cdk_macros::*;
    
    #[import(canister = "multiply_deps")]
    struct CounterCanister;
    
    #[query]
    async fn read() -> candid::Nat {
        CounterCanister::read().await.0
    }
  6. Save your changes and close the main.rs file to continue.

  7. Create a Cargo.toml in this directory and open it in a text editor.

  8. Configure settings for your project.

    For example, you should have a Cargo.toml file with settings similar to the following for this tutorial:

    [package]
    name = "rust_deps"
    version = "0.1.0"
    authors = ["DFINITY <[email protected]>"]
    edition = "2018"
    
    [lib]
    path = "main.rs"
    crate-type = ["cdylib"]
    
    [dependencies]
    ic-cdk = { path = "../../../cdk-rs/src/ic-cdk", version = "0.1.1" }
    ic-cdk-macros = { path = "../../../cdk-rs/src/ic-cdk-macros", version = "0.1.1" }
    ic-types = "0.1.1"
    Replace the path to the ic-cdk and ic-cdk-macros packages with the appropriate path for your local computer.
  9. Save your changes and close the Cargo.toml file to continue.

  10. Create a new file named deps.did.

  11. Open the file in a text editor, then copy and paste the following service definition for the read function:

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

  13. List the contents of the src/rust_deps directory to verify you have the following files:

    Cargo.toml	deps.did	main.rs

Start the network and deploy locally

Before you can build the project, you need to connect to the Internet Computer network either running locally in your development environment or running remotely on a sub-network that you can access.

To start the network locally:

  1. Open a new terminal window or tab on your local computer and navigate to your project directory.

    For example, you can do either of the following if running Terminal on macOS:

    • Click Shell, then select New Tab to open a new terminal in your current working directory.

    • Click Shell and select New Window, then run cd ~/ic-projects/multiply_deps in the new terminal if your multiply_deps project is in the ic-projects working folder.

    You should now have two terminals open with your project directory as your current working directory.

  2. Start the Internet Computer network on your local computer by running the following command:

    dfx start

    After you start the local network, the terminal displays messages about network operations.

  3. Leave the terminal that displays network operations open.

  4. Switch to your second terminal window or tab.

  5. Register, build, and deploy the canister for the project by running the following command:

    dfx deploy

Invoke functions on the deployed canister

After successfully deploying the canister, you can test the canister by invoking the functions it provides. For this tutorial:

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

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

To test invoking methods on the deployed canister:

  1. Run the following command to invoke the read function from the Motoko program, 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. Run the following command to invoke the mul function to multiply the input argument by three:

    dfx canister call multiply_deps mul '(3)'

    The command returns the new value of the cell variable:

    (9)
  3. Run the following command to invoke the read function using the rust_deps program that imports the multiply_deps canister:

    dfx canister call rust_deps read

    The command returns the current value of the cell variable:

    (9)

Stop the local network

After you finish experimenting with your program, you can stop the local Internet Computer network so that it doesn’t continue running in the background.

To stop the local network:

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

  2. Stop the Internet Computer network by running the following command:

    dfx stop