Skip to main content

Rust Hello, world!

Beginner
Rust
Tutorial

Overview

In this guide, you'll learn the steps required to write, compile, deploy, and interact with a Rust backend canister.

This guide will showcase a simple 'Hello, world!' example.

Prerequisites

Before getting started, assure you have set up your developer environment according to the instructions in the developer environment guide and have created a Rust project.

For more information on project structure and code organization, review the project organization guide.

Writing the lib.rs file

Open the src/hello_world_backend/src/lib.rs file in a text editor. Replace the existing content with the following:

src/hello_world_backend/src/lib.rs
#[ic_cdk::query]
fn greet(name: String) -> String {
    format!("Hello there, {}! This is an example greeting returned from a Rust backend canister!", name)
}

Save the file.

Writing the Cargo.toml file

Open the src/hello_world_backend/Cargo.toml file in a text editor. Replace the existing content with the following:

src/hello_world_backend/Cargo.tom
[package]
name = "hello_world_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.8.2"
ic-cdk = "0.7.0"

Save the file.

Deploying the canister locally

Create

First, create an empty canister for the canister code to be installed into. To create the canister, run the command:

dfx start --clean --background
dfx canister create hello_world_backend

The output will resemble the following:

Creating canister hello_world_backend...
hello_world_backend canister created with canister id: br5f7-7uaaa-aaaaa-qaaca-cai

Build

Next, you need to compile your program into a WebAssembly module that can be deployed on ICP by building the canister. To build the canister, run the command:

dfx build hello_world_backend

Install

Then, install the compiled code into your canister with the command:

dfx canister install hello_world_backend

Deploy

To deploy the canister, use the command:

dfx deploy hello_world_backend

This command deploys just the hello_world_backend canister. To deploy all canisters in the dfx.json file, use the command:

dfx deploy

Deploying the canister to the mainnet

To deploy to the mainnet, you will need a balance of cycles.

Learn more about how to get cycles.

Once you have gotten cycles, check the status of the mainnet to confirm that your local environment can connect to it. You can ping the mainnet with the command:

dfx ping ic

The output should resemble the following:

{
  "ic_api_version": "0.18.0"  "impl_hash": "d639545e0f38e075ad240fd4ec45d4eeeb11e1f67a52cdd449cd664d825e7fec"  "impl_version": "8dc1a28b4fb9605558c03121811c9af9701a6142"  "replica_health_status": "healthy"  "root_key": [48, 129, 130, 48, 29, 6, 13, 43, 6, 1, 4, 1, 130, 220, 124, 5, 3, 1, 2, 1, 6, 12, 43, 6, 1, 4, 1, 130, 220, 124, 5, 3, 2, 1, 3, 97, 0, 129, 76, 14, 110, 199, 31, 171, 88, 59, 8, 189, 129, 55, 60, 37, 92, 60, 55, 27, 46, 132, 134, 60, 152, 164, 241, 224, 139, 116, 35, 93, 20, 251, 93, 156, 12, 213, 70, 217, 104, 95, 145, 58, 12, 11, 44, 197, 52, 21, 131, 191, 75, 67, 146, 228, 103, 219, 150, 214, 91, 155, 180, 203, 113, 113, 18, 248, 71, 46, 13, 90, 77, 20, 80, 95, 253, 116, 132, 176, 18, 145, 9, 28, 95, 135, 185, 136, 131, 70, 63, 152, 9, 26, 11, 170, 174]
}

Then, to deploy the canister to the mainnet, use the command:

dfx deploy hello_world_backend --network ic

For all commands that are intended to interact with the mainnet, the --network ic flag needs to be used.

Interacting with the canister

To test the canister's functionality, call one of the canister's methods directly from the command line. In this example, this canister only has one method, greet, so that will be the one tested.

To test this method, make a call to the greet method with the command:

dfx canister call hello_world_backend greet everyone

The output will resemble:

("Hello there, everyone! This is an example greeting returned from a Rust backend canister!")

You can replace the last input string with any name you'd like, such as:

dfx canister call hello_world_backend greet Bob

This will return the output:

("Hello there, Bob! This is an example greeting returned from a Rust backend canister!")

In addition to testing the canister's functionality, the canister can be interacted with using other dfx commands. For example:

To deposit cycles from the wallet into the canister, use the command:

dfx canister deposit-cycles [cycles amount] [canister-name]

To get the identifier of a canister, use the command:

dfx canister id [canister-name]

To get a canister's Wasm module hash and its current controllers, use the command:

dfx canister info [canister-name]

Testing the canister with PocketIC

PocketIC is a lightweight, deterministic testing solution for programmatic testing of canisters that seamlessly integrates with existing testing infrastructure, such as cargo test.

You can learn more about PocketIC and how to install it here.

For Rust canisters, PocketIC can be imported into your project with the code:

use pocket_ic::PocketIc;

Then, in your code, you can create a new PocketIC instance with the code:

let pic = PocketIc::new();

The following is a complete example for testing a counter canister:

use candid::encode_one;
use pocket_ic::PocketIc;

 #[test]
 fn test_counter_canister() {
    let pic = PocketIc::new();
    // Create an empty canister as the anonymous principal and add cycles.
    let canister_id = pic.create_canister();
    pic.add_cycles(canister_id, 2_000_000_000_000);

    let wasm_bytes = load_counter_wasm(...);
    pic.install_canister(canister_id, wasm_bytes, vec![], None);
    // 'inc' is a counter canister method.
    call_counter_canister(&pic, canister_id, "inc");
    // Check if it had the desired effect.
    let reply = call_counter_canister(&pic, canister_id, "read");
    assert_eq!(reply, WasmResult::Reply(vec![0, 0, 0, 1]));
 }

fn call_counter_canister(pic: &PocketIc, canister_id: CanisterId, method: &str) -> WasmResult {
    pic.update_call(canister_id, Principal::anonymous(), method, encode_one(()).unwrap())
        .expect("Failed to call counter canister")
}

View additional PocketIC testing examples.