What does this higher-ranked trait bound mean?
Asked Answered
S

2

11

In Salsa, there is a higher-ranked trait bound on a trait. I've seen HRTBs on function definitions but not on a trait. What does it mean?

pub trait Query: Debug + Default + Sized + for<'d> QueryDb<'d> {
...
    fn query_storage<'a>(
        group_storage: &'a <Self as QueryDb<'_>>::GroupStorage,
    ) -> &'a Arc<Self::Storage>;
}

https://github.com/salsa-rs/salsa/blob/fc6806a/src/lib.rs#L370

As in, how should I read this? Is it saying that, for any Query, there is a corresponding QueryDB that has some lifetime?

How is this different from

pub trait Query<'d>: Debug + Default + Sized + QueryDb<'d>

aside from that impls cannot specify 'd?

Santonin answered 2/1, 2022 at 16:23 Comment(0)
L
6

how should I read this?

It means that any implementation of Query also implements QueryDb<'d> for all possible values of 'd (i.e. all lifetimes) at once. Therefore, in a generic context, the trait bound T: Query implies T: for<'d> QueryDb<'d>.

How is this different from

pub trait Query<'d>: Debug + Default + Sized + QueryDb<'d>

aside from that impls cannot specify 'd?

By repeating the lifetime parameter on Query, it means that all trait bounds T: Query will need to be changed to T: for<'d> Query<'d> in order to be equivalent to the version where the HRTB is in Query itself.

This is basically a workaround for the lack of generic associated types. With generic associated types, QueryDb would instead look like this:

pub trait QueryDb: Sized {
    /// Dyn version of the associated trait for this query group.
    type DynDb<'d>: ?Sized + Database + HasQueryGroup<Self::Group> + 'd;

    /// Associate query group struct.
    type Group: plumbing::QueryGroup<GroupStorage = Self::GroupStorage>;

    /// Generated struct that contains storage for all queries in a group.
    type GroupStorage;
}

Before the pull request that introduced this lifetime parameter, QueryDb wasn't a separate trait; its members were part of Query. The generic associated type would allow us to merge QueryDb back into Query.

After reading the comments on that pull request, I get the impression that this change didn't yield the expected results. The goal was to allow a bound different from the implied 'static on associated type DynDb, but since every Query implements QueryDb<'d> for all possible 'd, that means every Query implements QueryDb<'static>. Therefore, in all implementations of QueryDb, the DynDb cannot possibly borrow anything with a lifetime shorter than 'static, otherwise the implementation of Query wouldn't be allowed (the bound for<'d> QueryDb<'d> wouldn't be satisfied).

Licentious answered 8/1, 2022 at 3:46 Comment(1)
is this obsolete in new code now that Rust 1.65 has GATs?Gaylagayle
V
0

It has to do with the type in the argument for query_storage:

Self as QueryDb<'_>

From the nomicon:

for<'a> can be read as "for all choices of 'a", and basically produces an infinite list of trait bounds that F must satisfy.

https://doc.rust-lang.org/nomicon/hrtb.html

Vortumnus answered 3/1, 2022 at 19:41 Comment(2)
But this can be Self as QueryDb<'d> if trait Query<'d>: ... + QueryDb<'d>!Lheureux
This doesn't fully answer the question, would you be up for elaborating?Santonin

© 2022 - 2025 — McMap. All rights reserved.