7
votes

I have created a trait for transforming from some values to a type I need. That conversion is already covered by From/Into for many types, but not everything I want. I thought I could exploit this, but quickly got an error "upstream crates may add a new impl of trait".

(stripped-down example in the playground)

pub trait Cookable {
    fn cook(self) -> (String, Vec<i8>);
}

impl<T: Into<Vec<i8>>> Cookable for T {
    fn cook(self) -> (String, Vec<i8>) {
        (String::from("simple"), self.into())
    }
}

impl Cookable for &str {
    fn cook(self) -> (String, Vec<i8>) {
        (String::from("smelly"), vec![self.len()])
    }
}

That triggers the following error:

error[E0119]: conflicting implementations of trait `Cookable` for type `&str`:
  --> src/lib.rs:11:1
   |
5  | impl<T: Into<Vec<i8>>> Cookable for T {
   | ------------------------------------- first implementation here
...
11 | impl Cookable for &str {
   | ^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `&str`
   |
   = note: upstream crates may add a new impl of trait `std::convert::From<&str>` for type `std::vec::Vec<i8>` in future versions

I am worried that the only way to work around this error is to specify individual trait implementations for every one of the types that already has an Into.

2
Most of the text of those answers was explaining why the error is a good idea. I am currently leaning toward the "use a macro to help you build a lot of specific impls instead of using the external helper trait", but maybe the tech has advanced since then.Mutant Bob

2 Answers

4
votes

It's not a problem you can "work around". It's a limitation imposed by the compiler to prevent future changes to your dependencies from subtly changing the behavior of your code.

For now, you avoid the error by implementing for concrete types instead of using generics and traits. A macro is one way to reduce the amount of keyboard entry you have to do.

In the future, some form of specialization might also be useful to solve this. However, this sits squarely in the middle of the reason that specialization isn't stable. It's possible to use this type of specialization to create unsound Rust using only safe code. A reduced form of specialization is being worked on, but it deliberately eschews the ability to specialize based on a trait and only works for concrete types.

See also:

2
votes

Based on the answers to the questions listed by Shepmaster, I have come up with the following workaround which appears to accomplish my mission. It is an implementation of the "use a macro to compactly do all the boilerplate of implementing the trait for a bunch of structs":

extern crate jni;
use jni::objects::{JObject, JValue};
use jni::JNIEnv;

pub trait ConvertRustToJValue<'a> {
    fn into_jvalue(self, je: &JNIEnv<'a>) -> JValue<'a>;
}

macro_rules! impl_convert_rust_to_jvalue {
    ( $($t:ty),* ) => {
    $( impl<'a> ConvertRustToJValue<'a> for $t
    {
        fn into_jvalue(self, _je:&JNIEnv) -> JValue<'a>
        {
        self.into()
        }
    }) *
    }
}

impl_convert_rust_to_jvalue! { i8, i16, i32, i64 }