Crate persisted

Source
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 macros
  • serde: Enable Serialize/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.
PersistedLazy
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.
PersistedLazyRefMut
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.
PersistedRefMut
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.
SingletonKey
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§

PersistedContainer
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.
PersistedKey
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.
PersistedStore
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§

PersistedKey
Derive macro for PersistedKey