8 unstable releases (3 breaking)

0.3.1 Aug 27, 2024
0.3.0 Aug 17, 2024
0.2.2 Aug 9, 2024
0.1.1 Jun 21, 2024
0.0.1 Jun 14, 2024

#780 in Database interfaces

Download history 164/week @ 2024-06-09 321/week @ 2024-06-16 35/week @ 2024-06-23 12/week @ 2024-06-30 16/week @ 2024-07-07 30/week @ 2024-07-21 262/week @ 2024-07-28 172/week @ 2024-08-04 169/week @ 2024-08-11 87/week @ 2024-08-18 235/week @ 2024-08-25 77/week @ 2024-09-01 27/week @ 2024-09-08

527 downloads per month
Used in 2 crates (via slumber_tui)

MIT license

30KB
276 lines

persisted

Test CI crates.io docs.rs

persisted is a Rust library that makes it easy and quick to save arbitrary program state. Its simple and flexible design means you bring your own data store. You tell persisted how to save data and what you want to save, and it figures out the rest.

For examples, see the examples/ directory or the documentation.


lib.rs:

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

Dependencies

~0.4–1MB
~21K SLoC