DEV Community

Kate Korsaro
Kate Korsaro

Posted on

Persisting data in Rust with Heave

As a developer, I often find myself working on small personal projects where I need a quick and easy way to persist data. Whether it's a simple command-line tool or a small web service, the need for data persistence is a recurring theme. For these kinds of projects, setting up a full-fledged database with a rigid schema can feel like overkill. I wanted something that was lightweight, flexible, and allowed me to evolve my data structures without hassle.

This led me to create heave, a small Rust library designed for exactly this purpose. My primary goal was to have a way to dump data into a SQLite database with minimal friction. SQLite is a perfect fit for small projects: it's serverless, self-contained, and easy to embed within an application.

Embracing Flexibility with the Entity-Attribute-Value (EAV) Model

One of the core design decisions in heave is the use of the Entity-Attribute-Value (EAV) model. If you're not familiar with EAV, it's a data model that represents data as a collection of three-part tuples:

  • Entity: The object you want to describe (e.g., a user, a product).
  • Attribute: A property of the entity (e.g., "name", "price").
  • Value: The value of the attribute (e.g., "Alice", 9.99).

The main advantage of the EAV model is its flexibility. You can add new attributes to entities without having to alter the underlying database schema. This is a huge win for projects where the data structure is likely to change over time. It allows for a more "fluid" approach to data modeling, which is exactly what I needed for my personal projects.

The EAV Trait: Bridging the Gap Between Structs and Entities

To make the process of working with the EAV model in Rust as seamless as possible, heave introduces the EAV trait. This trait provides a bridge between your custom Rust structs and the EAV representation of your data.

By implementing the EAV trait for your structs, you can easily convert them into a "fluid entity" that can be persisted to the database. The trait requires you to define a class for your struct, which is used to group entities of the same type in the database. For a type T that implements EAV, it's implied that From<Entity> for T and Into<Entity> for T (via From<T> for Entity) are also implemented.

Here's a conceptual example of how it works:

// Your custom struct
struct Product {
    id: String,
    name: String,
    model: Option<String>,
    price: u64,
    in_stock: bool,
}

// Implement the EAV trait for your struct
impl EAV for Product {
    fn class() -> &'static str {
        "product"
    }
}
Enter fullscreen mode Exit fullscreen mode

Once you've implemented the EAV trait, you can use heave's persistence functions to save your Product instances to the SQLite database. The library takes care of transforming the struct's fields into the EAV format and storing them in the appropriate tables.

Idiomatic Conversions with the From<T> Trait

To make the conversion process even more idiomatic and seamless for Rust developers, heave leverages the standard From<T> trait. This trait is the idiomatic way in Rust to handle conversions between different types. In heave, it's used in two key ways to map your custom structs to and from heave's internal Entity representation.

From Your Struct to a Heave Entity

First, you implement From<YourStruct> for Entity. This allows you to easily convert an instance of your data-carrying struct into a "fluid" Entity that heave can understand and persist. This conversion is typically where you would map your struct's fields to the attributes of the Entity.

Here's a conceptual look at the implementation:

impl From<Product> for Entity {
    fn from(product: Product) -> Self {
        let mut entity = Entity::new::<Product>()
            .with_id(&product.id)
            .with_attribute("name", product.name)
            .with_attribute("price", product.price)
            .with_attribute("in_stock", product.in_stock);
        if let Some(model) = product.model {
            entity.set("model", model);
        }
        entity
    }
}
Enter fullscreen mode Exit fullscreen mode

With this in place, you can simply use .into() to perform the conversion in your code, making it clean and readable:

let my_product = Product {
    id: "prod-123".to_string(),
    name: "Laptop".to_string(),
    model: Some("PenguinX".to_string()),
    price: 1200,
    in_stock: true,
};
let entity: Entity = my_product.into();
Enter fullscreen mode Exit fullscreen mode

From a Heave Entity Back to Your Struct

Conversely, you also implement From<Entity> for YourStruct. This enables you to transform an Entity loaded from the database back into your strongly-typed custom struct. This is where you'd map the attributes from the Entity back to your struct's fields, handling potential type conversions.

impl From<Entity> for Product {
    fn from(entity: Entity) -> Self {
        Product {
            id: entity.id.clone(),
            name: entity.unwrap("name"),
            model: entity.unwrap_opt("model"),
            price: entity.unwrap_or("price", 0),
            in_stock: entity.unwrap("in_stock"),
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

This allows for an equally straightforward conversion when retrieving data:

// Assume 'entity_from_db' is an Entity loaded from heave
let product: Product = entity_from_db.into();
Enter fullscreen mode Exit fullscreen mode

By using the standard From<T> trait for these mappings, heave provides a familiar and ergonomic API that integrates smoothly into typical Rust code, reducing boilerplate and improving clarity.

Getting Started with Heave

heave is still in its early stages of development, but it already provides the core functionality for persisting and loading data using the EAV model. The library is designed to be simple and easy to integrate into your projects.

If you're looking for a lightweight and flexible way to handle data persistence in your Rust projects, I encourage you to give heave a try. I'm excited to see how it can help other developers who share the same need for a simple, no-fuss persistence solution.

You can find the source code for heave on GitHub at https://github.com/katekorsaro/heave. Contributions are always welcome, whether it's bug reports, feature requests, or pull requests.

Top comments (0)