Oleh Devua
Oleh Devua

Reputation: 384

How to express that a returned associated type implements a trait?

This is abstract example that shows my problem, extracted from my attempts to refactor some Rust code and learn Rust simultaneously.

struct GenStruct<T> {
    field: T,
}

trait Marker {}
trait Return {}

impl Marker for i32 {}
impl Marker for u32 {}

// actually implement `Return` for GenStruct<M: Marker>,
// but compiler don't recognize that
impl Return for GenStruct<i32> {}
impl Return for GenStruct<u32> {}

struct Fake;

trait Trait<M: Marker> {
    type Ret: Return;
    fn meth(m: M) -> Self::Ret;
}

impl<M: Marker> Trait<M> for Fake {
    type Ret = GenStruct<M>;

    fn meth(m: M) -> GenStruct<M> {
        GenStruct { field: m }
    }
}

Output:

error[E0277]: the trait bound `GenStruct<M>: Return` is not satisfied
  --> src/lib.rs:23:17
   |
23 | impl<M: Marker> Trait<M> for Fake {
   |                 ^^^^^^^^ the trait `Return` is not implemented for `GenStruct<M>`
   |

The compiler doesn't recognize that I actually implement Return for every GenStruct<M> where M is Marker. To fix it, I can write something like:

trait Marker {
    fn is_i32() -> bool;
}
trait Return {
    fn ret();
}

impl Marker for i32 {
    fn is_i32() -> bool {
        true
    }
}
impl Marker for u32 {
    fn is_i32() -> bool {
        false
    }
}

// compiler is satisfied by such implementation
impl<M: Marker> Return for GenStruct<M> {
    fn ret() {
        if M::is_i32() {
        } else {
        }
    }
}

or use a trait object:

impl<M: Marker> Return for GenStruct<M> {}

trait Trait<'a, M: Marker + 'a> {
    fn meth(m: M) -> Box<Return + 'a>;
}

impl<'a, M: Marker + 'a> Trait<'a, M> for Fake {
    fn meth(m: M) -> Box<Return + 'a> {
        Box::new(GenStruct { field: m })
    }
}

However, if I use a trait object, I cannot write a specialized implementation of Return for GenStruct<i32> and GenStruct<u32>.

Will the compiler be able to recognize that I do implement GenStruct<M: Marker> or is my code is not idiomatic Rust? If my code is not idiomatic, then what is the right way to write it?

Upvotes: 2

Views: 914

Answers (2)

Shepmaster
Shepmaster

Reputation: 432089

// actually implement `Return` for GenStruct<M: Marker>,
// but compiler don't recognize that
impl Return for GenStruct<i32> {}
impl Return for GenStruct<u32> {}

Why do you think that you are implementing Return for any GenStruct<M: Marker>? This only implements it for two specific variants. The types that implement a trait are not a closed set; anyone can add new implementations of the trait in the future. The Rust designers don't want to allow the changes of a downstream crate to affect the compilation of your crate — that way lies madness!

impl<M: Marker> Return for GenStruct<M> {
    fn ret() {
        if M::is_i32() { } else { }
    }
}

This is the correct way of saying "for every M that implements Marker, GenStruct<M> implements Return. It doesn't matter how or where someone implements the traits, because there is guaranteed to be an implementation.

I cannot write a specialized implementation

That's correct, for now. There's an ongoing RFC, RFC 1020: impl specialization that aims to allow specialization in these cases.

Upvotes: 3

Francis Gagn&#233;
Francis Gagn&#233;

Reputation: 65937

Using a where clause, we can add an additional constraint on the generic impl so that it only applies if GenStruct<M> indeed implements Return.

impl<M: Marker> Trait<M> for Fake
where
    GenStruct<M>: Return,
{
    type Ret = GenStruct<M>;

    fn meth(m: M) -> GenStruct<M> {
        GenStruct { field: m }
    }
}

Rust 1.33 even indicates this via help text:

   = help: consider adding a `where GenStruct<M>: Return` bound

Upvotes: 6

Related Questions