Rust limitations on Wasm
Overview
WebAssembly (Wasm) is a binary code format and does not provide network access, a filesystem, or other functionalities other than pure computation. Anything function that needs to communicate outside of the Wasm module must use an IC API. This guide details limitations imposed by Wasm on canisters written in Rust.
Wasm limitations
- 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.
Crate limitations
Most crates work on ICP, but anything that performs input/output will not. Typically, it is fine to use a crate that performs input/output as long as you don't call the function that executes it. If you try to deploy a canister that uses this workflow, ICP will return an error about importing a function from "env"
.
Crates that specifically have a Wasm mode usually do not work as expected, as they usually have a special mode for executing in the browser and will perform their functionality using JavaScript functions instead, which don’t exist on ICP. If you try to deploy a canister that uses this workflow, ICP will return an error about importing a function named __wbindgen_describe
.
One example is the getrandom
crate, which usually pulled in from another dependency. If you tried to import uuid
with the v4
feature, it refuses to compile into Wasm, but if you enable its Wasm mode, it just gives you a different error, because it’s trying to use JavaScript.
Instead, getrandom
allows you to create your own random function with the register_custom_getrandom!
macro. The following boilerplate will eliminate the error and allow other crates to fetch randomness like usual:
thread_local! {
// A global random number generator, seeded when the canister is initialized
static RNG: RefCell<Option<StdRng>> = RefCell::new(None);
}
#[init]
fn init() {
init_rng();
}
// Static variables are cleared on upgrade, so also initialize it on post-upgrade
#[post_upgrade]
fn post_upgrade() {
init_rng();
}
// Randomness on the IC is async, and the custom getrandom function can't be async,
// so we seed an RNG instead of always calling raw_rand directly
fn init_rng() {
// raw_rand is technically an inter-canister call, and you can't make those from lifecycle functions like #[init],
// so we schedule an immediate timer to make the call instead
ic_cdk_timers::set_timer(Duration::ZERO, || ic_cdk::spawn(async {
let (seed,) = ic_cdk::api::management_canister::main::raw_rand().await.unwrap();
// StdRng is from the `rand` crate. It makes for a good default but any RNG implementation would work
RNG.with(|rng| *rng.borrow_mut() = Some(StdRng::from_seed(seed.try_into().unwrap())));
}));
}
register_custom_getrandom!(custom_getrandom);
fn custom_getrandom(buf: &mut [u8]) -> Result<(), getrandom::Error> {
RNG.with(|rng| rng.borrow_mut().as_mut().unwrap().fill_bytes(buf));
Ok(())
Crates in the category no-std
should typically work as expected.
Additional crate limitations include:
- 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.