With this library you can:
* use stable variables in your code - they store their data completely in stable memory, so you don't have to do your regular routine serializing/deserializing them in pre_updage
/post_upgrade
hooks
* use stable collections, like SVec
and SHashMap
which work directly with stable memory and are able to hold as many data as the subnet would allow your canister to hold
```toml
[dependencies] ic-stable-memory = "0.0.1" ```
Check out the example project to find out more.
Let's suppose, you have a vector of strings, which you want to persist between canister upgrades. For every data chunk which is small enough (so it would be cheap to serialize/deserialize it every time you use it) , you can use stable variables to store it in stable memory:
```rust use icstablememory::utils::memcontext::stable; use icstablememory::utils::vars::{getvar, initvars, reinitvars, setvar, storevars}; use icstablememory::{ initallocator, reinitallocator, };
type MyStrings = Vec
fn init() { // initialize the library
// grow you stable memory (if it wasn't used before) for at least one page
stable::grow(1).expect("Out of memory");
// initialize the stable memory allocator
init_allocator(0);
// initialize the stable variables collection
init_vars();
// create the stable variable
set_var("my_strings", &MyStrings::new()).expect("Unable to create my_strings stable var");
}
fn preupgrade() { // save stable variables meta (your data is already in stable memory, but you have to save the pointer to it, so it could be found after the upgrade) storevars(); }
fn postupgrade() { // reinitialize stable memory and variables (it's cheap) reinitallocator(0); reinit_vars(); }
fn getmystrings() -> MyStrings {
getvar::
fn addmystring(entry: String) {
let mut mystrings = getvar::
set_var("my_strings", &my_strings).expect("Out of memory");
} ```
This would work fine for any kind of small data, like settings. But when you need to store bigger data, it may be really inefficient to serialize/deserialize gigabytes of data just to read a couple of kilobytes from it. For example, if you're storing some kind of an event log (which can grow into a really big thing), you only want to access some limited number of entries at a time. In this case, you want to use a stable collection.
```rust use icstablememory::collections::vec::SVec; use icstablememory::utils::memcontext::stable; use icstablememory::utils::vars::{getvar, initvars, reinitvars, setvar, storevars}; use icstablememory::{ initallocator, reinitallocator, };
// Note, that Vec transformed into SVec
type MyStrings = SVec
fn init() { // this init function body looks the same as it was in the previous example, but now we create a different stable_variable
stable::grow(1).expect("Out of memory");
init_allocator(0);
// we still have to use a stable variable in order to save SVec's pointer in it, to persist it between upgrades
init_vars();
// now, our stable variable will hold an SVec pointer instead of the the whole Vec as it was previously
set_var("my_strings", &MyStrings::new()).expect("Unable to create my_strings stable var");
}
fn preupgrade() { // the same as before storevars(); }
fn postupgrade() { // the same as before reinitallocator(0); reinit_vars(); }
fn getmystringspage(from: u64, to: u64) -> MyStringsSlice {
let mystrings = getvar::
// our stable collection can be very big, so we only return a page of it
let mut result = MyStringsSlice::new();
for i in from..to {
let entry: String = my_strings.get_cloned(&i).expect(format!("No entry at pos {}", i).as_str());
result.push(entry);
}
result
}
fn addmystring(entry: String) {
let mut mystrings = getvar::
// this call now pushes new value directly to stable memory
my_strings.push(entry).expect("Out of memory");
// only saves SVec's meta, instead of the whole collection
set_var("my_strings", &my_strings).expect("Out of memory");
} ```
There is also a SHashMap
collection, if you need keyed values.