Defining the database struct

Now that we have defined a jar, we need to create the database struct. The database struct is where all the jars come together. Typically it is only used by the "driver" of your application; the one which starts up the program, supplies the inputs, and relays the outputs.

In calc, the database struct is in the db module, and it looks like this:


#![allow(unused)]
fn main() {
#[derive(Default)]
#[salsa::db(crate::Jar)]
pub(crate) struct Database {
    storage: salsa::Storage<Self>,

    // The logs are only used for testing and demonstrating reuse:
    //
    logs: Option<Arc<Mutex<Vec<String>>>>,
}
}

The #[salsa::db(...)] attribute takes a list of all the jars to include. The struct must have a field named storage whose type is salsa::Storage<Self>, but it can also contain whatever other fields you want. The storage struct owns all the data for the jars listed in the db attribute.

The salsa::db attribute autogenerates a bunch of impls for things like the salsa::HasJar<crate::Jar> trait that we saw earlier.

Implementing the salsa::Database trait

In addition to the struct itself, we must add an impl of salsa::Database:


#![allow(unused)]
fn main() {
impl salsa::Database for Database {
    fn salsa_event(&self, event: salsa::Event) {
        eprintln!("Event: {event:?}");
        // Log interesting events, if logging is enabled
        if let Some(logs) = &self.logs {
            // don't log boring events
            if let salsa::EventKind::WillExecute { .. } = event.kind {
                logs.lock()
                    .unwrap()
                    .push(format!("Event: {:?}", event.debug(self)));
            }
        }
    }
}
}

Implementing the salsa::ParallelDatabase trait

If you want to permit accessing your database from multiple threads at once, then you also need to implement the ParallelDatabase trait:


#![allow(unused)]
fn main() {
impl salsa::ParallelDatabase for Database {
    fn snapshot(&self) -> salsa::Snapshot<Self> {
        salsa::Snapshot::new(Database {
            storage: self.storage.snapshot(),
            logs: self.logs.clone(),
        })
    }
}
}

Implementing the traits for each jar

The Database struct also needs to implement the database traits for each jar. In our case, though, we already wrote that impl as a blanket impl alongside the jar itself, so no action is needed. This is the recommended strategy unless your trait has custom members that depend on fields of the Database itself (for example, sometimes the Database holds some kind of custom resource that you want to give access to).