Using the Candid UI with a Rust canister
Overview
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 inter-operation between services and frontends written in different programming languages, including Rust.
Prerequisites
Before getting started, assure you have set up your developer environment according to the instructions in the developer environment guide.
Interacting with a service from a Rust canister
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 simple counter application can use a Candid interface to interact with a Rust canister.
This example assumes you have a project made with the dfx new
command, as described in the quick start or deploying canisters pages.
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:
service : {
"inc": () -> ();
"read": () -> (nat) query;
"write": (nat) -> ();
}
Using Candid UI in the browser
To use Candid UI in the browser, first you need to deploy your canister. Using the src/counter_backend/src/lib.rs
and src/counter_backend/src/counter.did
files provided above, after these have been saved in a dfx
project, they can be deployed with the command:
dfx deploy
If you need to create a new dfx
project to insert these files into, please see the deploying canisters page.
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:
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:
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:
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, Rust CDK examples and the Rust tutorials.