Trait bound on type alias

I'm trying to use create a type alias, but I want to specify that the aliased type need to implement a trait, so I wrote this:

type MyAlias : MyTrait = MyType;

trait MyTrait {}

struct MyType {}

impl MyTrait for MyType {}

but rustc is not happy:

error: bounds on `type`s in this context have no effect
 --> src/lib.rs:1:16
  |
1 | type MyAlias : MyTrait = MyType;
  |                ^^^^^^^

Ok this is not possible. But why type alias with trait bound are possible inside a trait then? Like this:

trait MyOtherTrait {}

struct MyOtherType {}

impl MyOtherTrait for MyOtherType {}

trait MyTrait {
    type MyAlias: MyOtherTrait;
}

struct MyType {}

impl MyTrait for MyType {
    type MyAlias = MyOtherType;
}

If we are able to check if a given type does implement a trait inside the implementation of a trait I don't see why we should not be able to check this outside an implementation of a trait.

My use case is conditional compilation where the type aliased depends on the build environment like this:

#[cfg(target_arch = "x86_64")]
type MyAlias = A;
#[cfg(target_arch = "riscv64")]
type MyAlias = B;

What do y'all think? I there some workaround I missed? Is it useless? Do you see other use case for this?

I agree it's a hole that should be filled, though I'd phrase it more as "why aren't bounds enforced on type aliases" instead of "why are trait bounds enforced on associated types". Here's the issue.

Here's a workaround (perhaps there are better ones):

type MyAlias /*: MyTrait*/ = MyType;
struct MyAliasGuard<T: MyTrait + ?Sized>([T; 0]);
static MY_ALIAS_GUARD: MyAliasGuard<MyAlias> = MyAliasGuard([]);

Edit: e.g. of better, by supporting non-Sized aliases...

use core::marker::PhantomData;
type MyAlias /*: MyTrait*/ = MyType;
struct MyAliasGuard<T: MyTrait + ?Sized>(PhantomData<T>);
static MY_ALIAS_GUARD: MyAliasGuard<MyAlias> = MyAliasGuard(PhantomData);

If I understand your use-case correctly, as a workaround using something like
static_assertions::assert_impl_all - Rust

might work for you.

The reason why associated types allow trait bounds is that those are useful in generic functions that use those associated types. It simplifies working with traits such as IntoIterator that you can write T: IntoIterator instead of requiring a more lengthy T: IntoIterator, T::IntoIter: Iterator everywhere. However, concrete type aliases can't be used in any abstract contexts (unlike associated types for traits, which can be used in generic functions with a trait bound), instead they always stand for concrete known types and a type Foo = Bar; means that simply Foo implements all traits that Bar implements.


If your use case is more than just wanting a test for whether all cfg'd possible definitions fulfill the same common trait, then you might be interested in abstracting the type definition further (so that all information about the type that's left for the user is the trait bound). In this case, in the future a feature like type_alias_impl_trait might be useful for you, or right now you could use a wrapper struct with a cfg'd field instead of a type alias, so the type is actually the same, regardless of target_arch.

I agree.

I see how wrapping is a good way to deal with that but I'm not a big fan of statics. Thanks for the suggestion.

This does the tricks indeed (I did not tried it yet but it's what I want to do in a nutshell). Thanks I did not knew this project.

It's just a test. I tried to see what would happen if I forgot to implement the trait for one object here. The error from rustc is understandable and the guess on missing trait is good but I fear that at some point many trait might have same function name (let's face it finding names is the biggest difficulty in programming) and the comment will get unreadable or with too many suggestions.

I tried it since I'm already in nightly. While this does the tricks it introduces some weird pattern. For example if I store this alias as a field of a struct, I now need in the new function of this struct to build a concrete type. So if at this stage I need to know the concrete type, why not directly put it as the type of the filed (I hope I did not lost you here ^_^).

That was my first idea but this mean that my wrapper type need to implement my trait, just to forward it down to the type that I store. For one type why not but I will have a bunch of them and this will add a lot of boilerplate over time

Fair enough. I also thought of

trait MyTraitGuard {
    type Mirror: ?Sized + MyTrait;
}

impl<T: ?Sized + MyTrait> MyTraitGuard for T {
    type Mirror = Self;
}

type MyAlias = <MyType as MyTraitGuard>::Mirror;

But if static_assertions meets your needs, makes sense to just use that.