14

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.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Mutant Bob
  • 3,121
  • 2
  • 27
  • 52
  • 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 `impl`s instead of using the external helper trait", but maybe the tech has advanced since then. – Mutant Bob Jul 28 '20 at 15:26

2 Answers2

10

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:

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • 10
    If that's a maybe-future-problem, why wouldn't the compiler fail then in the future, when this is indeed a problem? Why does it have to fail now, when there is no conflict still? Since it will fail on compilation later anyway, what is the benefit of failing sooner at all? Just wondering if you know the reason. Thanks! – guymguym Nov 29 '21 at 22:29
  • @guymguym I think it is to avoid making [*automatic transition to new Rust edition*](https://doc.rust-lang.org/edition-guide/editions/transitioning-an-existing-project-to-a-new-edition.html) more difficult. – ynn Sep 10 '22 at 07:08
6

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 }
Mutant Bob
  • 3,121
  • 2
  • 27
  • 52