How to express nested trait bounds

I have multiple traits required by some other type, so I just combined them into one trait like this

pub trait SceneSyncBackend
where
    <<Self as SceneSyncBackend>::Position as Component>::Storage: Tracked,
    <<Self as SceneSyncBackend>::SceneData as Component>::Storage: Tracked,
{
    type Position: Position + Component;
    type SceneData: SceneData + Component + Send + Sync;
    type DropEntity: DropEntity;
}

But the nested trait bounds express is cumbersome, I have to add it everywhere, Is there any simpler way to express this?

This is indeed hard or impossible to do in a nice way in Rust. One workaround Iโ€™m aware of is the following, but itโ€™s usefulness depends a bit on your particular use case.

pub trait IsEqual {
    type To: ?Sized;
}
impl<T: ?Sized> IsEqual for T {
    type To = Self;
}

trait ComponentWithTrackedStorage: Component {
    type TrackedStorage: Tracked + IsEqual<To = Self::Storage>;
}

impl<T: ?Sized> ComponentWithTrackedStorage for T
where
    T: Component,
    T::Storage: Tracked,
{
    type TrackedStorage = Self::Storage;
}

pub trait SceneSyncBackend {
    type Position: Position + ComponentWithTrackedStorage;
    type SceneData: SceneData + ComponentWithTrackedStorage + Send + Sync;
    type DropEntity: DropEntity;
}

There are โ€œtricksโ€ to convince the rust compiler that T::TrackedStorage and T::Storage are indeed the same type using this IsEqual<To=โ€ฆ> trait bound when thatโ€™s needed. If you like this workaround and your code requires such a trick in a few places, feel free to share more details and I can give you some more details on how that โ€œtrickโ€ works.

Another approach is to use a macro to expand to all the (redundantly) required bounds.

Example usage:

with_SceneSyncBackend_bounds!($T_is_SceneSyncBackend = bounds_for!<T> in {
    pub
    fn foo<T> ()
    where
        $($T_is_SceneSyncBackend)*
    {}
    
    pub
    fn bar<T> ()
    where
        $($T_is_SceneSyncBackend)*,
    {}

    /// Static assertions
    const _: () = {
        fn assert_tracked<Arg : Tracked> ()
        {}

        fn forall<T> ()
        where
            $($T_is_SceneSyncBackend)*,
        {
            let _ = assert_tracked::<
                <
                    <T as SceneSyncBackend>::Position
                    as
                    Component
                >::Storage
            >;
        }
    };
});
1 Like

This thread so far clearly demonstrates that Rust still lacks all of the following:

  • the ability to โ€œpack upโ€ more complicated where clauses / constraints / bounds involving a type Foo as a trait or trait alias that can be used as a bound Foo: ๐‘บ๐’๐’Ž๐’†๐’•๐’‰๐’Š๐’๐’ˆ
  • good compiler support for type equality
  • macros expanding to (the contents of) where clauses

Iโ€™m positive that weโ€™ll eventually get support for all of the above in the who-knows-how-distant future

3 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.