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. When the wrapper is dropped, the a request is made to the store to save the final value.

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 = 1;
println!("Selected: {}", people.selected().name);
// Selected: Susan
drop(people);

let people = SelectList::new(list);
// The previous value was restored
assert_eq!(*people.selected_index, 1);
println!("Selected: {}", people.selected().name);
// Selected: Susan

Structs§

  • 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 drop.
  • 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_persisted. Similarly, the persisted value that’s loaded at initialization isn’t stored directly in the container. Instead, PersistedContainer::set_persisted determines how to initialize state based on it.
  • 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§

  • 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.
  • 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.
  • 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§