Skip to main content

Using the Candid UI with a Rust canister

Intermediate
Rust
Tutorial

Candid is an interface description language with the primary purpose of describing the public interface of a service. A service is usually in the form of a program deployed as a canister. Candid is language-agnostic, meaning that it allows for the interoperation between services and frontends written in different programming languages, including Rust.

Interacting with a service from a Rust canister

For developing Rust canisters on ICP, you will need the following tools and packages:

Install the IC SDK. Note: While using the IC SDK is the typical path for most developers, experienced Rust developers may choose to circumvent IC SDK entirely and use the Rust CDK directly.
Download and install Rust.
Download and install the wasm32-unknown-unknown target: rustup target add wasm32-unknown-unknown

When writing a canister in Rust, the dfx build command will ensure that the canister's service description is correctly referenced. The Candid service description, however, will need to be manually written following the writing conventions described in the Candid specification.

In versions of the Rusk CDK v0.11.0 and higher, the Candid export workflow has been changed. You can call the ic_cdk::export_candid!() macro to enable the Candid export behavior, then use the candid-extractor to extract the Candid from the canister's Wasm.

The following example code displays how a Candid interface can be used to interact with a simple counter application written in Rust.

src/counter_backend/src/lib.rs
use ic_cdk::{
    api::call::ManualReply,
    export::{candid, Principal},
    init, query, update,
};
use std::cell::{Cell, RefCell};

thread_local! {
    static COUNTER: RefCell<candid::Nat> = RefCell::new(candid::Nat::from(0));
    static OWNER: Cell<Principal> = Cell::new(Principal::from_slice(&[]));
}

#[init]
fn init() {
    OWNER.with(|owner| owner.set(ic_cdk::api::caller()));
}

#[update]
fn inc() {
    ic_cdk::println!("{:?}", OWNER.with(|owner| owner.get()));
    COUNTER.with(|counter| *counter.borrow_mut() += 1u64);
}

#[query(manual_reply = true)]
fn read() -> ManualReply<candid::Nat> {
    COUNTER.with(|counter| ManualReply::one(counter))
}

#[update]
fn write(input: candid::Nat) {
    COUNTER.with(|counter| *counter.borrow_mut() = input);
}

This canister would use the following Candid file:

src/counter_backend/counter_backend.did
service : {
  "inc": () -> ();
  "read": () -> (nat) query;
  "write": (nat) -> ();
}

Using Candid UI in the browser

To use Candid UI in a web browser, first you need to deploy your canister.

If you need to learn how to create a new dfx project to insert these files into, please see the introduction to Rust canisters.

dfx deploy

The output of the dfx deploy command will resemble:

Deployed canisters.
URLs:
  Frontend canister via browser
    counter_frontend: http://127.0.0.1:4943/?canisterId=ajuq4-ruaaa-aaaaa-qaaga-cai
  Backend canister via Candid interface:
    counter_backend: http://127.0.0.1:4943/?canisterId=aovwi-4maaa-aaaaa-qaagq-cai&id=a4tbr-q4aaa-aaaaa-qaafq-cai

To use the Candid UI, navigate to the link specified as the 'Backend canister via Candid interface' URL. In the browser, the Candid UI will look like this:

Candid UI

This UI interface can be used to call the functions of the counter canister.

Using Candid for interactions between canisters

For example, if you want to write a hello canister that calls the counter canister in Rust, the code for the hello canister will resemble the following:

src/hello_backend/src/lib.rs
use ic_cdk_macros::*;

#[import(canister = "counter")]
struct Counter;

#[update]
async fn greet() -> String {
    let result = Counter::inc(1.into()).await;
    format!("The current counter is {}", result)
}

What this code does

When the import macro on the counter canister (the #[import(canister = "counter")] declaration) is processed by the dfx build command, the dfx build command ensures that the counter canister identifier and the Candid description are passed to the Rust CDK correctly.

The Rust CDK then translates the Candid type into the appropriate native Rust type. This translation enables you to call the inc method natively, as if it were a Rust function, even if the counter canister is implemented in a different language or if you do not have the source code for the imported canister.

For additional information on the type mapping between Candid and Rust, you can consult the supported types reference section.

For other canisters and tools to interact with the hello canister, you need to manually create a .did file with the content:

src/hello_backend/hello_backend.did
service : {
    greet : () -> (text);
}

In versions of the Rusk CDK v0.11.0 and higher, the Candid export workflow has been changed. You can call the ic_cdk::export_candid!() macro to enable the Candid export behavior, then use the candid-extractor to extract the Candid from the canister's Wasm.

References

For additional information and libraries to help you create Candid services or canisters in Rust, see the documentation for the Candid crate and Rust CDK examples.