Let trait aliases be used in impls, or how to add io::{Read, Write} to libcore

Part 1: Trait Aliases

The trait_alias unstable feature enables syntax like this:

#![feature(trait_alias)]

trait Foo = std::fmt::Debug + Send;
trait Bar = Foo + Sync;

// Use trait alias as bound on type parameter.
fn foo<T: Foo>(v: &T) {
    println!("{:?}", v);
}

However, the resulting traits (Foo and Bar) in the above example cannot be implemented on types.

error[E0404]: expected trait, found trait alias `Foo`
 --> src/main.rs:8:6
  |
8 | impl Foo for A {
  |      ^^^ not a trait

My suggestion comes in two parts:

  1. Enable trait aliases to contain multiple regular traits, not just a single regular trait and multiple auto traits.
  2. Implementing a trait alias for a type would be the same as if that trait alias were a regular trait with all of the member functions, types, and constants of the constituent traits combined (name conflicts would be a compile error), except that implementing the trait alias implements the constituent traits instead.

Part 2: Adding io::Read and io::Write to core

If the above suggestion was implemented, Read and Write traits could be added to core as subsets of the full trait in std. For example (using the full paths, even though it's invalid syntax):

trait core::io::Read {
    type Error;
    fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error>;
    // ... the other methods that don't require allocation
}

And then, std::io::Read would be reimplemented as a trait alias:

trait std::io::Read = core::io::Read<Error = std::io::Error> + alloc::io::ReadExt;

where

trait alloc::io::ReadExt: core::io::Read {
    // ... all the read methods that require allocation
}

The same applies to the std::io::Write trait. It would require careful planning to make sure there was no breakage, but I think this would be possible.


I'm very curious what people think of this proposal—whether it's desirable to even add io traits to core, whether this proposal would actually work, and any comments or criticism.

4 Likes

Assuming it can be implemented and works how described, it sounds like a good idea to me!

1 Like

This is already the case. "Trait alias" is a misnomer: it's actually an alias for the RHS of a where clause. For example, trait Trivial = ; and trait Static = 'static; are both valid trait aliases.

I think that what you're actually suggesting here is permitting folks to write

impl A + B for C {
  // ...
}

where this would expand into impl A for C and impl B for C in the "obvious" manner. However, there are three problems:

  • Should impl for T {} work? Presumably it should do nothing.
  • Should impl 'a for T {} work? Like, should it just check that T: 'a?
  • What if A and B share a function with the same name but different signatures? What if they share an associated type with the same name? What does Self::Assoc mean now?

I'm not saying this is a bad idea, but it's significantly more subtle than what you propose.

(I guess this isn't the worst idea. One could imagine a hypothetical "generic impl" that makes use of this, e.g. impl<trait Tr> Tr for Thing<Tr> { ... }, but trait generics are a distant dream.)

2 Likes