-
Notifications
You must be signed in to change notification settings - Fork 155
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow lifetime annotations in database_storage!
and query_group!
#104
Conversation
Extend the `database_storage!` macro such that it allows for lifetimes to be added to the generated struct. This is necessary if the database itself has lifetime parameters that need to be threaded through to the queries being invoked.
database_storage!
database_storage!
and query_group!
I tried to adjust the
We're enumerating the different My usual go-to approach in this situation is to use recursion to perform the outer product. In this case however I can't get the parser to expand a macro in bounds position, e.g.
I'm pretty much stuck on this. The only option I see would be to have the user type the query type twice (as in |
Extend the `query_group!` macro to allow for lifetimes to be annotated on the generated structs and traits.
Remove the `'static` bound from the `Query` trait. This allows databases with lifetime parameters to be used.
Add the `tests/refs.rs` test which creates a simple database that holds a reference to an input string.
Added the complementary changes to Some future work:
|
database_storage!
and query_group!
database_storage!
and query_group!
Explored this a little bit further. In the implementation of struct DatabaseImpl<'a> {
arena: typed_arena::Arena<SomeType<'a>>,
}
trait Database<'a>: salsa::Database {
fn alloc(&'a self, x: SomeType<'a>) -> &'a SomeType<'a>;
}
impl<'a> Database<'a> for DatabaseImpl<'a> { ... }
query_group! { ... }
database_storage! { ... }
fn my_query<'a>(db: &impl Database<'a>) {
db.alloc( ... ); // borrow checker sadness here
} The call to alloc does not check out as it requires One way around this would be to not have the arena be part of the database, but rather keep a reference to it and have the arena survive the database. But then in the case that we want to keep a per-database arena for temporary values, e.g. for snapshots, we would want the arena embedded within the database. Maybe we could add an associated type fn my_query<'a>(db: Database<'a>::Handle) { ... } Not sure if that would help though. Edit: Maybe we could add an argument to the two macros that allows an explicit lifetime to be annotated on database refs. This would allow the user to pick one of the lifetimes they annotated and declare that the "database lifetime". Or maybe we could make the database lifetime explicit somewhere in |
Just bumped into the drop checker when trying to allocate into an arena held by the database, since it cannot guarantee that refs held in other fields of the database struct are dropped before the arena field. Not sure how to work around this... looks like the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice! The main thing that gives me pause is the need to add PhantomData
to QueryType
, but I don't see an immediate way around it. It's probably worth merging this (perhaps generalized to >1 lifetime parameter) for now, as it is not very disruptive, and then re-considering the query_mut(X)
structure to see if there is another way (at minimum, you could certainly imagine query_mut::<X>()
, which in a way might be more clear).
@@ -540,26 +540,28 @@ where | |||
#[macro_export] | |||
macro_rules! query_group { | |||
( | |||
$(#[$attr:meta])* $v:vis trait $name:ident { $($t:tt)* } | |||
$(#[$attr:meta])* $v:vis trait $name:ident $(<$lt:lifetime>)* { $($t:tt)* } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if, while we're at it, we want to support other sorts of parameters? Regardless, this should probably be something like $(<$($lt:lifetime,)*>)?
, so that one can use the syntax <'a, 'b>
(I forget, is ?
stable yet..?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That was stabilized end of Nov (rust-lang/rust#48075), but hasn't made it into 1.31.1.
|
||
$( | ||
#[derive(Default, Debug)] | ||
$v struct $QueryType<$lt>(std::marker::PhantomData<&$lt ()>); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similarly here I would use a phantomdata like:
PhantomData<($(&$lt (),)*)>
which then also works regardless of how many lifetimes were provided.
But this is going to be a drag: some of the APIs really rely on being able to type Query
as a value and have it work. Hmm.
Good reason to push harder on rust-lang/rfcs#2357 =)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or, alternatively, perhaps a reason to rejigger some of those APIs..
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
e.g., db.query_mut(Foo).set(...)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I don't like this either... I don't see why the query struct couldn't be independent of the database's lifetime, after all it's just a means of naming the query. But my brain hasn't been able to come up with anything better 😞
I would expect the arena to be outside the database, yes. This is the general pattern in Rust when using an arena, for better or worse. |
Oh, I just saw this. I guess this is why it's currently handling at most one lifetime etc. Hmm. Maybe this argues for the move to procedural macros =) |
I should really have read all your comments before reviewing, I guess =) |
No worries, I dumped a lot of text 🙈 |
Yes that might actually be a good solution. Could also help in unifying the two macros probably? |
I think the problem regarding lifetimes on query structs is the following. Assume we have a query #[derive(Debug, Default)]
struct Uppercase;
impl<'a, DB> salsa::Query<DB> for Uppercase
where
DB: Database<'a>,
{
type Key = String;
type Value = &'a str;
type Storage = salsa::plumbing::MemoizedStorage<DB, Self>;
}
impl<'a, DB> salsa::plumbing::QueryFunction<DB> for Uppercase
where
DB: Database<'a>,
{
fn execute(db: &DB, key: String) -> &'a str {
uppercase(db, key)
}
} This fails with
So we would need something that for any |
Manually implement `Debug` for query types since the addition of the phantom data field causes queries to be printed as `MyQuery(PhantomData)`. The new implementation prints them as `MyQuery` again.
I'm going to close this for now, since we are trying to implement the new procedural macro based approach first. |
Extend the
database_storage!
macro such that it allows for lifetimes to be added to the generated struct. This is necessary if the database itself has lifetime parameters that need to be threaded through to the queries being invoked.The main goal is to allow arena allocation to be used in your database struct, and to pass those references around as query inputs and outputs.
Not sure this is the best solution -- I'd love to hear your thoughts.