Multiple trait "default" implementations

Hey I want to make shorthands/defaults of specific implementations of another trait (code example below) with the importance of the final type still having the base Account trait, for generics.

mod auth {
    pub trait Account {
        fn id(&self) -> &u32;
        fn from_cookies(&str) -> Self;
    }

    pub trait TokenAccount {
        fn id(&self) -> &u32;
    }

    impl<T: TokenAccount> Account for T {
        fn id(&self) -> &u32 {
            <T as TokenAccount>::id(self)
        }

        fn from_cookies(&str) -> Self {
            // serde stuff, serialize, deserialize, whatever, according to token functionality
        }

    }

    pub trait JwtAccount {
        fn id(&self) -> &u32;
    }

    impl<T: JwtAccount> Account for T {
        fn id(&self) -> &u32 {
            <T as JwtAccount>::id(self)
        }

        fn from_cookies(&str) -> Self {
            // serde stuff, serialize, deserialize, whatever, according to jwt functionality
        }

    }
}


struct Account {
    id: u32,
    username: String,
}

impl auth::TokenAccount for Account {
    fn id(&self) -> &u32 {
        &self.id
    }
}

fn main() {
    let acc = Account {
        id: 1,
        username: "user1".to_string(),
    };

    println!("id: {}", acc.id);
    println!("username: {}", acc.username);
}

Are blanket implementations like this an antipattern? i know i could have a TokenAccount<T> struct instead but then accessing fields becomes annoying, the naming is weird TokenAccount<Account>, and it's also longer so type aliasing will become a must... If it's an antipattern, any suggestions on achieving similar functionality to this that is more idiomatic?

I know this can probably also be done with macros but I feel like macros are escaping the problem, I don't like how macros hide away functionality behind user defined, well, macro rules, instead of using the language. I feel like macros are better suited for boilerplate code, instead of being complex implementations.

I basically just want to have "supertraits" with the sole purpose of having a specific default implementation of the "subtrait", whether that be with the supertrait mechanism or not.

It's not an antipattern per-se but not currently supported by Rust's type system.
How would the compiler reconcile conflicting implementations if you'd also add a TokenAccount implementation?

The usual thing to do is to generalize the base trait:

    pub trait Account<T: Authentication> {
        fn id(&self) -> &u32;
        fn from_cookies(&str) -> Self;
    }

and then implement something like:

impl auth::Account<Token> for Account {
    ...
}

Here's one way.

mod auth {
    pub trait CookiesInto<Acct> {
        fn cookies_into(_: &str) -> Acct;
    }
    
    pub enum Token {}
    impl<Acct: Account> CookiesInto<Acct> for Token { ... }

    pub enum Jwt {}
    impl<Acct: Account> CookiesInto<Acct> for Jwt { ... }

    pub trait Account {
        type FromCookiesVia: CookiesInto<Self> where Self: Sized;
        fn id(&self) -> &u32;
        fn from_cookies(s: &str) -> Self where Self: Sized {
            <Self::FromCookiesVia>::cookies_into(s)
        }
    }
}

impl auth::Account for Account {
    type FromCookiesVia = auth::Token;
    fn id(&self) -> &u32 {
        &self.id
    }
}

logically, we can use different associated type equality constraints to prove the non-overlapping of blanket implementations, but currently the type checker does not implement this feature. we want something like this to compile, but it does not (at least not for now).

trait A {
    type M;
}

trait B {}

impl<T> B for T where T: A<M = u8>  {}
impl<T> B for T where T: A<M = u16> {}

but there's workaround existing, using an indirection via a helper trait. see issue #20400 and the disjoint_impls crate for details. the short version loosk like this:

// same as above
trait A {
	type M;
}
trait B {
	fn b(&self);
}

// a private helper, methods have same signature of B
trait BB<T> {
	fn bb(&self);
}
// the indirection/delegation
impl<T> B for T
where
	T: A, //<--- not `T: A<M = SomeType>`
	T: BB<<T as A>::M>,
{
	fn b(&self) {
		self.bb();
	}
}

// the actually disjoint blanket impls
// note these impls are NOT the same trait, so it compiles
impl<T> BB<u8> for T where  T: A<M = u8> {
	fn bb(&self) { todo!("the u8 case") }
}
impl<T> BB<u16> for T where T: A<M = u16> {
	fn bb(&self) { todo!("the u16 case") }
}

back to your original problem, you cannot have two blanket impls like this:

impl<T> Account for T where T: TokenAccount { ... }
impl<T> Account for T where T: JwtAccount { ... }

because the two bounds T: TokenAccount and T: JwtAccount are not provable to be disjoint.

you already mentioned on solution, that is use two different generic wrapper types so they are non-overlapping.

the other solution is to use the associated type trick, something like this:

mod auth {
	// same as before
	pub trait Account {
		fn id(&self) -> &u32;
		fn from_cookies(cookies: &str) -> Self;
	}

	// this is the helper trait, can be private
	// for lack of better name, same signature as Account
	trait AccountHelper<FromCookies> {
		fn id(&self) -> &u32;
		fn from_cookies(cookies: &str) -> Self;
	}

	/// instead of two differnt traits `TokenAccount` and `JwtAccount`,
	/// it's now a single trait with associated type tag,
	/// I give it a meaningful name instead of just `Tag`.
	/// it's impossible to overlap between:
	/// - `AccountCategory<Tag = A>`, and
	/// - `AccountCategory<Tag = B>`
	pub trait AccountCategory {
		type FromCookies;
		fn id(&self) -> &u32;
	}
	// the tags
	pub enum Token {}
	pub enum Jwt {}

	/// a blanket impl delegating to the helper trait
	impl<T> Account for T
	where
		T: AccountCategory,
		T: AccountHelper<T::FromCookies>,
	 {
		fn id(&self) -> &u32 {
			<Self as AccountHelper<_>>::id(self)
		}
		fn from_cookies(cookies: &str) -> Self {
			<Self as AccountHelper<_>>::from_cookies(cookies)
		}
	}

	/// the "default" blanket impls, except this time
	/// they are disjoint and it actually compiles
	impl<T> AccountHelper<Token> for T where T: AccountCategory<FromCookies = Token> { ... }
	impl<T> AccountHelper<Jwt> for T where T: AccountCategory<FromCookies = Jwt> { ... }
}

struct Account {
	id: u32,
	username: String,
}

/// instead of `impl TokenAccount for Account {}`
/// now its `impl AccountCategory for Account { FromCookies = Token }` 
impl auth::AccountCategory for Account {
	type FromCookies = auth::Token;
	fn id(&self) -> &u32 {
		&self.id
	}
}

note: this is more aligned with your original design, and it's slightly different from what @quinedot suggested.

Yeah that's an interesting solution, you basically made a trait shortcut which implements other traits with specific implementations. But notice that still the core solution to the problem of recyclable trait implementations is just using ZSTs with the desired implementation.

Personally I feel like the desired solution is the ability for subtraits to override default implementations and implement required methods of their supertraits. And then in the rust docs for each trait you'll see the total required methods to implement on the list on the left to make it clear if the subtrait has already implemented some of the supertraits.

So then code like this will be possible:

trait Account {
  fn id(&self) -> u32;
  fn from_cookies(&str) -> Self;
}

trait TokenAccount: Account + Deserialize {
  fn <Self as Account>::from_cookies(&str) -> Self {
    // deserialize stuff here...
  }
}
...
struct Account { ... }

impl auth::Account for Account {
  fn id(&self) -> u32 { Account.id }
}

impl auth::TokenAccount for Account;

ExactSizeIterator's new implementation with proposed functionality

I can also take the ExactSizeIterator subtrait as an example, it doesn't have any required methods, but the documentation instructs you to override the size_hint implementation of its supertrait, Iterator. So optimized implementations can use the len provided trait method from ExactSizeIterator, that gets its info from size_hint, hopefully improving performance.

I feel like implementing ExactSizeIterator is unclear, and forces you to read the docs. I know this may sound stupid but I feel like having documentation is a privilege, and for the same reason I think documentation should not be required in order to understand how to use something. A language as expressive as Rust should be (and usually is) understandable without documentation in my opinion.

Which is why I think with the proposed subtrait can implement supertrait functionality, ExactSizeIterator should require a len method, which is what the dev implements when implementing ExactSizeIterator, instead of implementing size_hint from Iterator. This len method is instead of the len provided method from ExactSizeIterator that just returns a usize that it gets from the implemented size_hint. But before the "old" len returns the usize, it makes sure with assert_eq! that both of the bounds received from size_hint are equal.

I can see 2 performance gains from this new implementation, ("old" len being the current implementation in std):

  1. Old len returns a usize but it gets it from an fn size_hint -> (usize, Option<usize), so memory is wasted from the unneeded bounds. New len just returns a usize because it is the literal ExactSizeIterator implementation.
  2. Old len makes sure both the bounds returned from the size_hint are equal, for safety, with assert_eq!. For the same reason as the first performance gain, new len doesn't need to check anything.

old len source code

Maybe the compiler already optimizes away the "faults" I noted with the old len when compiling with optimizations. But I still think it could speed up optimized compilations because there are fewer things to optimize (maybe that's how it works?) and that it will also optimize non-optimized debug builds, of course.

And lastly, because of the proposed functionality, ExactSizeIterator can override size_hint from Iterator in order to keep the old functionality like so:

trait ExactSizeIterator: Iterator {
  fn <Self as Iterator>::size_hint(&self) -> (usize, Option<usize>) {
    let len = self.len();
    (len, Some(len))
  }
}

This avoids the boilerplate that there usually is when implementing ExactSizeIterator, where the implementor needs to return (len, Some(len)) from size_hint instead of just len. Though this is very little boilerplate, I imagine it could be much more significant for other traits.

Conclusion

I think this subtrait implement supertrait functionality would be highly beneficial for Rust. And in an unrelated note, I also think in general that there should be the ability to make trait methods private, so they are only accessible to the trait itself and its subtraits. The current solutions I've seen for private trait methods is to have ZSTs that can only be made where it can be called, which is the same module as the trait usually.

I feel like traits are a huge zero cost abstraction yet have such limited functionality, to me they are the core to reusability and modularity. I think they are the biggest (and maybe the only) pillar of polymorphism in Rust and with my proposed subtrait can implement supertrait functionality they will now also offer inheritance (with no expense at runtime, right?).

I am probably getting ahead of myself and people who are far smarter than me who contribute to the language aren't adding this functionality for a reason but I'd like to know why that is and the opinion of anyone who sees this.

Also, sorry about the wall of text.

I think you wish Rust had specialization.

struct A;

impl<'lt> Iterator for &'lt A {
    type Item = ();

    fn next(&mut self) -> Option<Self::Item> { None }
    fn size_hint(&self) -> (usize, Option<usize>) {
        // note that size_hint is not required to be accurate
        // except for TrustedLen implementors
        (1, Some(1))
    }
}
impl ExactSizeIterator for &'static A {
    fn len(&self) -> usize { 0 }
}

fn lifetime_is_nonstatic<'lt>() -> bool {
    let it: &'lt A = &A;
    it.size_hint().0 == 1
}
// now, we can specialize on 'static lifetime