1.1 Rust level 1
Rust is a powerful programming language known for its rich type system, memory efficiency, and safety guarantees. Much of the Internet Computer Protocol (ICP) stack is written in Rust because it is fast and ensures both memory safety and thread safety.
Rust is especially well-suited for developing canisters on the ICP because:
It compiles to WebAssembly and implements strict compile-time checks to help prevent common bugs.
Performs close to that of other low-level languages like C/C++.
Has a strong tooling ecosystem.
The ic-cdk
The ic-cdk
provides a library for writing canister code in Rust that can be compiled into a WebAssembly module that can be installed into a canister and deployed on ICP. Each canister must define entry points. An entry point can be exposed (or made 'public') so that it can be called by other canisters or users.
To use the ic-cdk
, first include it in a project's Cargo.toml
file:
[lib]
crate-type = ["cdylib"]
[dependencies]
ic-cdk = "0.17"
candid = "0.10" # required if you want to define Candid data types
Then, in a Rust source code file, typically <project_name>/<canister_name>/src/lib.rs
, define your canister's code, such as the following example that registers a query
entry point named hello
:
use ic_cdk::{query, update, init};
#[ic_cdk::query]
fn greet(name: String) -> String {
format!("Hello, {}!", name)
}
Canister entry points
The public entry points of a canister are called methods. They can be called by other canisters or external users. There are three primary types of Rust canister entry points:
init
: The initialization method of the canister.query
: A method that returns query data (read-only) from the canister.update
: A method that is allowed to make changes to the canister's state and return data from the canister.
init
If a canister's init
entry point is defined, it is the first function that the network calls when a canister is installed. If the execution of the init
entrypoint finishes successfully, the network considers the canister fully set up. But if it traps, the installation is canceled and the canister is rolled back to its previous state.
A canister's init
entry point should match the initialization parameters in the Candid interface. It must have no return value. Each canister can only have one init
entry point.
#[ic_cdk::init]
fn init_function() {
// ...
}
Refer to the canister_init
specification for more information.
query
A query
endpoint provides a callable method that only returns information. query
endpoints cannot alter the state of a canister.
#[ic_cdk::query]
fn query_function() {
// ...
}
If you specify a name for the method, the canister will expose the method on the public interface with that name. Otherwise, the name of the function will be used:
#[ic_cdk::query(name = "method_name")]
fn query_function() {
// ...
}
If a query
endpoint must have a prerequisite executed before it can be called, you can define a guard
function that must be executed before the query function can be executed. If the guard
function returns an error, the query
function does not execute.
fn guard_function() -> Result<(), String> {
// ...
}
#[ic_cdk::query(guard = "guard_function")]
fn query_function() {
// ...
}
update
An update
entry point provides a callable method that can make changes to the canister's state. update
methods may return a response or they may return an empty type.
#[ic_cdk::update]
fn update_function() {
// ...
}
update
methods can be named or configured to use guard
functions in the same ways that query
methods can.
Candid interface files
Candid is an interface description language (IDL) used to describe a canister's public methods. Each Candid file defines a service, then adds the canister's public methods and the data types that each method accepts and returns.
Here is an example that defines the public method greet
, which accepts type text
and returns type text
using a query
call.
service : {
"greet" : (text) -> (text) query;
};
Candid interface description files (.did
) for Rust canisters must be handwritten or manually exported by first defining the export_candid!
macro, which exposes a method called get_candid_pointer
. If defined, the candid-extractor
tool can be used to extract the canister's Candid file. This macro must be called once at the end of your canister's code outside of any query or update definitions.
use candid::Principal;
#[ic_cdk::query]
fn whoami() -> Principal {
ic_cdk::caller()
}
// Export the Candid interface
ic_cdk::export_candid!();
query
and update
methods can be hidden from the Candid exporter by setting hidden
to equal true
. If a method is hidden, it still exists in the canister but will not have a Candid interface generated through export_candid!
#[query(hidden = true)]
fn query_function() {
// ...
}
WebAssembly (Wasm)
WebAssembly (Wasm) is a portable binary format that runs in a virtual machine. ICP compiles canister code to standard Wasm using tools included in the ic-cdk
that compile Rust code to the wasm32-unknown-unknown
Wasm target.
Rust relies on the Wasm binary and the associated Candid interface file to define and expose the canister's methods. In a dfx.json
file that defines a Rust canister, you will need to specify additional fields to indicate where in the project these files are located:
package
field: Required; Specifies the package name as defined in the project'sCargo.toml
file.type
field: Required; defines the canister as type "Rust".candid
field: Required; the path to the Candid file that describes the canister's interface.
For example:
"hello_world": {
"candid": "src/hello_world/hello_world.did",
"package": "hello_world",
"type": "rust"
},
Alternatively, if you have specified the package
name in your Cargo.toml file, such as:
[package]
name = "hello_world_rust"
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.10"
ic-cdk = "0.17"
ic-cdk-timers = "0.11" # Feel free to remove this dependency if you don't need timers
Then you can omit the build
and wasm
fields in favor of the package
and type
fields:
"hello_world_rust": {
"candid": "src/hello_world_rust/hello_world_rust.did",
"package": "hello_world_rust",
"type": "rust"
},
Canisters written in Rust have a set of limitations imposed by the Wasm compiler due to the fact that the IC does not provide a file system, network access, or other functionalities at the moment. These limitations include:
You cannot create threads. Instead, use
ic_cdk::spawn
.You cannot sleep. Instead, use
ic_cdk_timers
.You cannot use
Instant
. Instead, useic_cdk::api::time
.You cannot access environment variables with
std::env::var
, but you can embed compile-time environment variables with theenv!
macro.Any crate that performs input/output will not work; however, if you use a crate that performs input/output, it may work as long as you don't call the function that executes it.
Crates that specifically have a Wasm mode usually do not work as expected. These crates will typically assume they are operating in a web browser, with access to the JS environment. Canisters instead execute in so-called 'headless' Wasm, with no standard environment besides the IC system functions.
You cannot use
tokio
, useic_cdk::spawn
.You cannot use crates that make or serve HTTP requests. Instead, use HTTP outcalls or the gateway API respectively.

Did you get stuck somewhere in this tutorial, or do you feel like you need additional help understanding some of the concepts? The ICP community has several resources available for developers, like working groups and bootcamps, along with our Discord community, forum, and events such as hackathons. Here are a few to check out:
- Developer Discord
- Developer Liftoff forum discussion
- Developer tooling working group
- Motoko Bootcamp - The DAO Adventure
- Motoko Bootcamp - Discord community
- Motoko developer working group
- Upcoming events and conferences
- Upcoming hackathons
- Weekly developer office hours to ask questions, get clarification, and chat with other developers live via voice chat.
- Submit your feedback to the ICP Developer feedback board