Skip to main content

Rust limitations on Wasm

Intermediate
Rust
Tutorial

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, use ic_cdk::api::time.
  • You cannot access environment variables with std::env::var, but you can embed compile-time environment variables with the env! 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: