owned this note
owned this note
Published
Linked with GitHub
# Salsa 1.0 versus 3.0
## Background/Takeaways
I tried to port rust-analyzer's `base-db` yesterday to new Salsa. I initially expected a 1:1 mapping, but I was (very!) wrong to: some concepts in the old Salsa do not map cleanly to the new Salsa, which may lead to an overall decrease in LoC in rust-analyzer. Here are some potentially incorrect takeaways:
- Old Salsa has everything in a database/trait: there are no free functions or structures. Old Salsa requires placing everything into a trait that masquarades as a query group
- New Salsa is a more fine-grained: it has databases (often traits), free tracked functions, and standalone, tracked or interned structs. It is _not_ necessary to group all queries into a single trait/[query group](https://github.com/salsa-rs/salsa-rfcs/blob/master/RFC0001-Query-Group-Traits.md) in the new Salsa.
## Comparison Between Old and New Salsa
## Basic, Hello World Comparison
Given the following in old Salsa:
```rust
struct InputId(u32);
#[salsa::query_group(OgSalsaDatabaseStorage)]
trait OgSalsaDatabase {
#[salsa::input]
fn field(&self, input_id: InputId) -> u32;
fn foo(&self, input: InputId) -> u32 {
self.field(input)
}
}
```
...this would be rewritten as the following in new Salsa:
```rust
#[salsa::input]
struct Input {
field: u32
}
#[salsa::tracked]
fn foo(db: &dyn crate::Db, input: Input) -> u32 {
input.field(db)
}
```
### Concrete `DefDatabase` Example
Let's consider a snippet of `DefDatabase` from `crates/hir-def/src/db.rs`:
```rust
#[salsa::query_group(DefDatabaseStorage)]
pub trait DefDatabase: InternDatabase + ExpandDatabase + Upcast<dyn ExpandDatabase> {
fn crate_supports_no_std(&self, crate_id: CrateId) -> bool;
#[salsa::input]
fn expand_proc_attr_macros(&self) -> bool;
#[salsa::invoke(DefMap::crate_def_map_query)]
fn crate_def_map(&self, krate: CrateId) -> Arc<DefMap>;
}
fn crate_supports_no_std(db: &dyn DefDatabase, crate_id: CrateId) -> bool {
let file = db.crate_graph()[crate_id].root_file_id();
// todo...
}
```
### `#[salsa::query_group]`
Like the name suggests, this annotation defines a query group, which contains *everything* that a block of queries need to execute (storage, namely), as well as the queries themselves. In the `DefDatabase` example, the queries are `expand_proc_attr_macros` and `crate_def_map`. As best as I can tell, this is necessary boilerplate in old Salsa and there is no equivalent in new Salsa.
This is how most databases and queries are defined (the above snippet is `DefDatabase::crate_supports_no_std`, but another [example is from`ide-db`](https://github.com/rust-lang/rust-analyzer/blob/3723e5910c14f0ffbd13de474b8a8fcc74db04ce/crates/ide-db/src/symbol_index.rs#L102-L149)):
```rust
#[salsa::query_group(SymbolsDatabaseStorage)]
pub trait SymbolsDatabase: HirDatabase + SourceRootDatabase + Upcast<dyn HirDatabase> {
/// The symbol index for a given module. These modules should only be in source roots that
/// are inside local_roots.
fn module_symbols(&self, module: Module) -> Arc<SymbolIndex>;
}
fn module_symbols(db: &dyn SymbolsDatabase, module: Module) -> Arc<SymbolIndex> {
let _p = tracing::info_span!("module_symbols").entered();
let symbols = SymbolCollector::collect_module(db.upcast(), module);
Arc::new(SymbolIndex::new(symbols))
}
```
There isn't an 1:1 equivalent in new Salsa, but I think the closest are [_tracked_ functions](https://salsa-rs.netlify.app/tutorial/parser#the-parse_statements-function):
```rust
#[salsa::input]
struct Module {
field: u32,
}
#[salsa::tracked]
fn module_symbols(db: &dyn SymbolsDatabase, module: Module) -> Arc<SymbolIndex> {
let _p = tracing::info_span!("module_symbols").entered();
let symbols = SymbolCollector::collect_module(db.upcast(), module);
Arc::new(SymbolIndex::new(symbols))
}
```
The primary syntactic difference is that a trait is not required to define or group queries: the plumbing happens automagically, upon first use.
### `#[salsa::input]`
Unlike derived queries, whose value is given by a function, input queries are explicitly set by doing `db.query(QueryType).set(key, value)`.
- For `expand_proc_attr_macros`, an example setter would be `self.db.set_expand_proc_attr_macros(true)`
- rust-analyzer tends to use the generated durabilty variant (`self.db.set_expand_proc_attr_macros_with_durability(true, Durability::HIGH)`) more often.
In the new Salsa, the equivalent API is [a builder API](https://github.com/salsa-rs/salsa/blob/d5018d5614a58a473825d65faacac1fa329faf4c/tests/tracked_fn_high_durability_dependency.rs#L21-L25):
```rust
let mut db = salsa::DatabaseImpl::default();
let input_high = MyInput::new(&mut db, 0);
input_high
.set_field(&mut db)
.with_durability(Durability::HIGH)
.to(2200);
```
### `#[salsa::invoke]`
For a non-input query, this indicates the function to call when a query must be recomputed. For `DefDatabase`, `DefDatabase::crate_def_map` will be invoking `DefMap::crate_def_map_query` instead. There isn't an equivalent in Salsa 3.0 due to the absence of `#[salsa::query_group]` and prominence of `#[salsa::tracked]`.
## Questions
- The version of Salsa that rust-analyzer uses requires placing everything into a trait/query group/database. What is the expected pattern for new Salsa?
- Naively, I'd expect that there'd be substantially more free functions annotated with `#[salsa::tracked]`. Is that correct?
- When should be be creating a new database? My impression from reading [this commnet](https://github.com/salsa-rs/salsa/pull/538#discussion_r1694250775) is that creating a database is useful for two things:
- Passing around state that _isn't_ tracked by Salsa (logs, maybe file IO, etc.)
- Faster compile times/better encapsulation, by keeping each database local to the crate that uses it.
- Does the Salsa entity changes ([PR](https://github.com/salsa-rs/salsa/pull/304), [document](https://hackmd.io/8pNav1yXQHiUmS6V2Bmstg)) (I assume it is the current precursor to the new Salsa) address [matklad's complaints](https://hackmd.io/o07mw34ITEiXgRaouTb4Yw#Matklads-complaints), especially around "[u]ser is reponsible for managing storage"?
- He says: "managing storage takes a fair amount of code, it would be cool to have ECS-style uniform storage, when you don't have to manually add fields to parent entities". It seems like new Salsa, with tracked/interned structs, accomplishes this. Is that correct?
- Old Salsa generates setters for values. For instance, `SourceDatabase::file_text` will have an automatically generated setter called `SourceDatabase::set_file_text_with_durability`. While it might be nice to keep a semantic equivalent, I think using an input/tracked struct's builder is preferrable. See this example: https://github.com/salsa-rs/salsa/blob/af2ec49d8060c16dab28e25111950eb7b09ff590/tests/input_field_durability.rs. This has a few interesting implications:
- I think we need one big trait of databases in order to make this feel more "right". I think `db: &dyn salsa::Db` doesn't cut the mustard.
- This means we can't have queries return plain, unadorned `Arc<[u8]>` or `Parse<ast::SourceFile>`: they need to wrapped with Salsa structs.
- The entire VFS module/functionality seems superfluous under new Salsa, but I think to make migration easier, we should bridge to it.