Reputation: 384
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
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
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