3

For example:

trait TraitX { }
trait TraitY { }
impl TraitX for TraitY { }

I figured it would mean the same as

impl<A: TraitY> TraitX for A { }

but the error message suggests otherwise:

$ rustc --version
rustc 0.12.0-nightly (a70a0374e 2014-10-01 21:27:19 +0000)
$ rustc test.rs
test.rs:3:17: 3:23 error: explicit lifetime bound required
test.rs:3 impl TraitX for TraitY { }
                          ^~~~~~

Does impl TraitX for TraitY (or some variant of it with an explicit lifetime) mean anything in Rust? If so, what is an example of its use?

Peter Hall
  • 53,120
  • 14
  • 139
  • 204
Snowball
  • 11,102
  • 3
  • 34
  • 51

2 Answers2

4

impl TraitX for TraitY is using TraitY as a dynamically sized type (DST). If we add the required lifetime bound (see e.g. this for more info about the necessity of the lifetime bound), the compiler will complain in this manner:

trait TraitX { }
trait TraitY { }
impl<'a> TraitX for TraitY+'a { }

fn main() {}

<anon>:3:1: 3:34 error: the trait `core::kinds::Sized` is not implemented for the type `TraitY+'a`
<anon>:3 impl<'a> TraitX for TraitY+'a { }
         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
<anon>:3:1: 3:34 note: the trait `core::kinds::Sized` must be implemented because it is required by `TraitX`
<anon>:3 impl<'a> TraitX for TraitY+'a { }
         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

playpen

The errors are saying that TraitY+'a is not sized, that is, it doesn't have a size known at compile time (e.g. u8 has size 1, Vec<T> is the size of 3 pointers).

The syntax is implementing TraitX for TraitY trait objects (these are covered in the "object types" section of the reference), allowing it to be handled (behind a pointer) in places where a value implementing TraitX is expected. The working usage involves some extra Sized? annotations, these say that whatever they're attached to is optionally (?) sized (the default is for things to be assumed to be sized).

#![allow(dead_code)]

// indicate that it's OK to implement this trait for DSTs
trait TraitX for Sized? { } 
trait TraitY { }
trait TraitZ { }

impl<'a> TraitX for TraitY+'a { }

// the Sized? is to allow DSTs to be passed to this.
fn example<Sized? T: TraitX>(_: &T) {}

fn call_it(x: &TraitY, _y: &TraitZ) {
    example::<TraitY>(x); // only possible if `TraitY` impls `TraitX`.

    // error:
    // example::<TraitZ>(_y);  // `TraitZ` doesn't impl `TraitX`.
}

fn main() {}

playpen

The explicit ::<TraitY> type hint is required when calling a function with an unsized type for now, but this is a bug #17178. For the moment, there's still quite a few bugs with DST so it's not easy actually to use in practice, but this will improve.

The major motivation for DST is making handling trait objects more consistent with other pointer types, e.g. we currently only support &Trait and Box<Trait> trait objects, but DST is designed to allow other pointer types like Rc<Trait>, Arc<Trait>. DST also allows treating those like real pointers, e.g. if obj: Box<Trait> then &*obj is only now possible with DST, previously it was illegal because trait objects are fat pointers, not normal pointers.

Community
  • 1
  • 1
huon
  • 94,605
  • 21
  • 231
  • 225
3

Rust has changed a lot since this question was asked. While that syntax is currently* still supported, trait objects should now be designated with the keyword dyn:

trait TraitX { }
trait TraitY { }
impl TraitX for dyn TraitY { }

This is entirely equivalent to the code in the question, but a bit more obvious about what it means: implement TraitX for the trait object dyn TraitY.

For example:

struct Thing;
impl TraitY for Thing {}

fn main() {
    // Treat the &Thing as a dynamic object
    let object: &dyn TraitY = &Thing;

    // The TraitY object can still be used where a TraitX is expected
    do_something(object);
}

fn do_something<T: TraitX + ?Sized>(t: &T) {
}

On the surface, as you mention, it seems similar to:

impl<A: TraitY> TraitX for A { }

This implements TraitX for any concrete type that implements TraitY, but does not include trait objects because Sized is always an implicit bound on type parameters. But we can remove that limitation by explicitly opting out of the Sized bound:

impl<A: TraitY + ?Sized> TraitX for A { }

This includes all concrete types that implement TraitY, but now also includes dynamic TraitY objects. For maximum flexibility, you should use this form instead of either of the alternatives above.


*"Currently" because future editions of Rust may require the keyword in these situations. At least, the default linter will disallow omitting it.

Peter Hall
  • 53,120
  • 14
  • 139
  • 204