Search code examples
rusttraitslifetime

What does this higher-ranked trait bound mean?


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?


Solution

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