Containers
A container is an abstraction that manages how some data should be stored and retrieved. Sometimes
this is as simple as a single value (Item
), sometimes there's more to it than
that.
For most of your needs, you can likely do what you need to with the built-in containers:
For more advanced use cases, you can build your own containers as described in the Implementing new containers section.
Namespace
To construct a container, you need to provide a single byte. This byte is used to distinguish between different "root" containers in the same storage space.
use cw_storey::containers::{Item, Map};
let item: Item<u32> = Item::new(0); // byte 0 is used as the namespace
let map: Map<String, Item<u32>> = Map::new(1); // byte 1 is used as the namespace
A container will assume it's free to save stuff under the given key (e.g. A
), and any key that
starts with that same byte (e.g. A28F
). In this way, a whole namespace is reserved for the
container.
An item will simply store its value under the given byte, while a map will store its values under keys that are prefixed with the given byte. How exactly the namespace is managed is up to the container implementation.
To avoid key collisions, you must provide a different byte to each container.
Accessor
Every container has a corresponding "accessor" type that is used to perform all read/write operations.
To get the accessor of a top-level container, the access
method (opens in a new tab) must be called. It takes a reference
to a storage backend.
use cw_storey::containers::Item;
use cw_storey::CwStorage;
let item: Item<u32> = Item::new(0);
item.access(&mut CwStorage(&mut storage))
.set(&42)
.unwrap();
assert_eq!(
item.access(&CwStorage(&storage))
.get()
.unwrap(),
Some(42)
);
Composition
Some containers can be composed together to create more complex data structures. Right now, the only
built-in container that supports composition is the Map
container.
This is an alternative design largely used to achieve the same effect as "composite keys" in
cw-storage-plus
(think tuple keys like (String, u32)
), but also make this more flexible - you
can put a Column
or any other container inside a Map
!
Let's compare the two approaches:
use cw_storage_plus::Map;
let map: Map<(String, String), u32> = Map::new("m");
map.save(&mut storage, ("foo".to_string(), "bar".to_string()), &42)
.unwrap();
assert_eq!(
map.load(&storage, ("foo".to_string(), "bar".to_string())),
Ok(42)
);
It's possible to define custom containers that enable composition similar to maps. If done properly, they can be mixed with any other containers, including built-in ones.
Encoding
Types like Item
or Column
need a way to encode values in a binary store. If you're using the
Item
and Column
types from cw-storey
, the MessagePack (opens in a new tab) format is used.
This is a binary encoding that should generally be a storage performance improvement over JSON.
If you need to use a different encoding, you can instead import the Item
/Column
type from the
storey
crate and specify an alternative encoding. A guide to implementing your encoding can be
found in the Alternative encodings section.