Expand description
persisted
is a library for persisting arbitrary values in your program so
they can easily be restored later. The main goals of the library are:
- Explicitness: You define exactly what is persisted, and its types
- Ease of use: Thin wrappers make persisting values easy
- Flexible:
persisted
is data store-agnostic; use any persistence scheme you want, including a database, key-value store, etc.
persisted
was designed originally for use in
Slumber, a TUI HTTP client. As such, its
main use case is for persisting values between sessions in a user interface.
It is very flexible though, and could be used for persisting any type of
value in any type of context. no_std
support means it can even be used in
embedded contexts.
§Concepts
persisted
serves as a middleman between your values and your data store.
You define your data structures and how your data should be saved,
and persisted
makes sure the data is loaded and stored appropriately. The
key concepts are:
- Data wrappers: Persisted and PersistedLazy
- These wrap your data to automatically restore and save values from/to the store
- Data store: any implementor of PersistedStore
- Key: A unique identifier for a value in the store. Each persisted value must have its own key. Key types must implement PersistedKey.
§How Does It Work?
persisted
works by wrapping each persisted value in either Persisted or
PersistedLazy. The wrapper is created with a key and optionally a default
value. A request is made to the store to load the most recent value for the
key, and if present that value is used. Whenever the value is modified, the
store is notified of the new value so it can be saved (see either
Persisted or PersistedLazy for a stricter definition of “is modified”).
Because the store is accessed from constructors and destructors, it cannot
be passed around and must be reachable statically. The easiest way to do
this is with either a static
or thread_local
definition of your store.
§Example
Here’s an example of a very simple persistence scheme. The store keeps just a single value.
use core::cell::Cell;
use persisted::{Persisted, PersistedKey, PersistedStore};
/// Store index of the selected person
#[derive(Default)]
struct Store(Cell<Option<usize>>);
impl Store {
thread_local! {
static INSTANCE: Store = Default::default();
}
}
impl PersistedStore<SelectedIndexKey> for Store {
fn load_persisted(_key: &SelectedIndexKey) -> Option<usize> {
Self::INSTANCE.with(|store| store.0.get())
}
fn store_persisted(_key: &SelectedIndexKey, value: &usize) {
Self::INSTANCE.with(|store| store.0.set(Some(*value)))
}
}
/// Persist the selected value in the list by storing its index. This is simple
/// but relies on the list keeping the same items, in the same order, between
/// sessions.
#[derive(PersistedKey)]
#[persisted(usize)]
struct SelectedIndexKey;
#[derive(Clone, Debug)]
#[allow(unused)]
struct Person {
name: String,
age: u32,
}
/// A list of items, with one item selected
struct SelectList<T> {
values: Vec<T>,
selected_index: Persisted<Store, SelectedIndexKey>,
}
impl<T> SelectList<T> {
fn new(values: Vec<T>) -> Self {
Self {
values,
selected_index: Persisted::new(SelectedIndexKey, 0),
}
}
fn selected(&self) -> &T {
&self.values[*self.selected_index]
}
}
let list = vec![
Person {
name: "Fred".into(),
age: 17,
},
Person {
name: "Susan".into(),
age: 29,
},
Person {
name: "Ulysses".into(),
age: 40,
},
];
let mut people = SelectList::new(list.clone());
*people.selected_index.get_mut() = 1;
println!("Selected: {}", people.selected().name);
// Selected: Susan
let people = SelectList::new(list);
// The previous value was restored
assert_eq!(*people.selected_index, 1);
println!("Selected: {}", people.selected().name);
// Selected: Susan
§Feature Flags
persisted
supports the following Cargo features:
derive
(default): Enable derive macrosserde
: EnableSerialize/Deserialize
implementations
Structs§
- Persisted
- A wrapper that will automatically persist its contained value to the store. The value will be loaded from the store on creation, and saved on mutation.
- Persisted
Lazy - Similar to Persisted, but the value that’s sent to the store is not the same as the value stored in memory. Instead, the value is computed at save time by PersistedContainer::get_to_persist. Similarly, the persisted value that’s loaded at initialization isn’t stored directly in the container. Instead, PersistedContainer::restore_persisted determines how to initialize state based on it.
- Persisted
Lazy RefMut - A guard encompassing the lifespan of a mutable reference to a lazy container. The purpose of this is to save the value immediately after it is mutated. The save will only occur if the value actually changed. A copy of the previous value is saved before the mutable access, and compared after the access.
- Persisted
RefMut - A guard encompassing the lifespan of a mutable reference to a persisted value. The purpose of this is to save the value immediately after it is mutated.
- Singleton
Key - A persisted key for a value type that appears only once in a program. The name of the value type is the only information available as the key, hence why the value type must only be used once.
Traits§
- Persisted
Container - A container that can store and provide a persisted value. This is used in conjunction with PersistedLazy to define how to lazily get the value that should be persisted, and how to restore state when a persisted value is loaded during initialization.
- Persisted
Key - A unique key mapped to a persisted state value in your program. A key can be any Rust value. Unit keys are useful for top-level fields that appear only once in state. Keys can also carry additional data, such as an index or identifier.
- Persisted
Store - A trait for any data store capable of persisting data. A store is the layer
that saves data. It could save it in memory, on disk, over the network, etc.
The generic parameter
K
defines which keys this store is capable of saving. For example, if your storage mechanism involves stringifyin keys, your implementation may look like:
Derive Macros§
- Persisted
Key - Derive macro for PersistedKey