Rust library `canister_tools` for simple upgrades, data-snapshots download and upload, and some handy tools

Hi everyone,

This post is about a new rust library for canisters: canister-tools.

The main features of this library make it easy to handle upgrades of global-data-variables in the main-heap-memory and at the same time simple to create serialized snapshots of the global-data, download the snapshots in chunks, and can upload a serialized snapshot in chunks and then load it onto the global variable(s) giving maximum controll over the global-heap-data.

The library works with the memory-manager’s virtual-memories feature of the ic-stable-structures library so each global variable is registered with a unique memory-id and is serialized it it’s own corresponding memory-id during upgrades. This makes it so that a canister can use stable-structures like a StableBTreeMap at the same time as holding global-variables in the main-heap.

This library is compatible with global-variables in a thread_local! with a RefCell as is the custom when writing rust canisters.

Usage of the library is as follows:


#[derive(Default, serde::Serialize, serde::Deserialize)]
struct Data {
    ...
}

thread_local! {
    static DATA: RefCell<Data> = RefCell::new(Data::default());
}
  
const DATA_MEMORY_ID: MemoryId = MemoryId::new(0);
  
#[init]
fn init() {
    canister_tools::init(&DATA, DATA_MEMORY_ID);
}  
  
#[pre_upgrade]
fn pre_upgrade() {
    canister_tools::pre_upgrade();
}

#[post_upgrade]
fn post_upgrade() {
    canister_tools::post_upgrade(&DATA, DATA_MEMORY_ID, None::<fn(Data) -> Data>);
}

When updating the global-variable struct fields or type during an upgrade, use the optional old_as_new convert function to deserialize as the old struct, then convert to the new struct using the function, then load onto the global-variable. Or pass None if the type stays the same.

Take a look at the docs for the candid file of the controller methods that can be used to create snapshots, download snapshots, and even upload snapshots onto the global-variables in the heap.

By default, types that implement serde’s Serialize and Deserialize traits are compatible out-of-the-box and use the bincode serialization format. The library is compatible with a different or custom serialization format by implementing the canister_tools::Serializable trait on the types.

I’m glad to help people use this library, let me know if there are questions.

:Levi.

7 Likes