Hello everyone, Rust newbie here.
I'm currently experimenting with a custom serialization module. (bincode does almost, but not quite do what I'm looking for.)
I've implemented my custom "Serialize" trait for integers without a hitch. However, when I try to add a generic impl for everything that impls AsRef<str>, I get an error about conflicting implementations for all integers.
I've narrowed it down to this minimal example (Playground):
<anon>:5:1: 9:2 error: conflicting implementations of trait `Foo` for type `i64`: [E0119]
<anon>:5 impl Foo for i64 {
<anon>:6 fn foo(&self) -> usize {
<anon>:7 0
<anon>:8 }
<anon>:9 }
<anon>:5:1: 9:2 help: see the detailed explanation for E0119
<anon>:11:1: 15:2 note: conflicting implementation is here:
<anon>:11 impl<S: AsRef<str>> Foo for S {
<anon>:12 fn foo(&self) -> usize {
<anon>:13 1
<anon>:14 }
<anon>:15 }
error: aborting due to previous error
playpen: application terminated with error code 101
I figured that the trait bound on S would be enough to make it not apply to types like i64, but this doesn't seem to be the case.
Could someone explain how exactly these two impls conflict and, if possible, what a workaround could be?
I don't believe the compiler currently uses trait bounds when deciding if two impls conflict. Can't locate this exact issue but it's related to specialization.
The compiler behaves this way to allow flexibility for upstream crates - even if a type doesn't implement some trait now, it may start to in the future. Allowing that combination of implementations (without specialization) would make the addition of any trait implementation a breaking change.
Aaah, okay, that is indeed a very useful abstraction.
FWIW, my workaround (until impl specializations are stable) involves the newtype pattern and a trait with a default function:
// Wrapper around selected "primitive" types.
struct Prim<N>(N);
trait Primitive> Sized {
fn max_size() -> usize {
std::mem::size_of::<Self>()
}
}
impl Primitive for Prim<i64> {}
// The same for u64, i32, ...
impl Primitive for Prim<isize> {
fn max_size(&self) -> usize { 8 }
}
// Same for usize ...
// So Prim<N> can be used like an N.
impl<N> std::ops::Deref for Prim<N> {
type Target = N;
fn deref(&self) -> &N { &self.0 }
}
Prim assures the compiler that the trait AsRef<str> trait won't suddenly appear in a future release and Primitive makes sure I can generically call methods on Prim:
trait Foo {
fn size(&self) -> usize;
}
impl<N> Foo for Prim<N> where Prim<N>: Primitive {
fn size(&self) -> usize { Self::max_size() }
}
impl<S: AsRef<str>> Foo for S {
fn size(&self) -> usize { self.as_ref().len() }
}
… which made me realize it would probably be easier to stick to concrete types only for my trait.