17

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.

0

2 Answers 2

12

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:

Sign up to request clarification or add additional context in comments.

3 Comments

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 I think it is to avoid making automatic transition to new Rust edition more difficult.
Is there a mechanism to tell the compiler that "OK, I know the upstream crate will never implement this trait"? Like a compile error?
7

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 } 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.