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).