How to define trait inheriting and extending the original trait?

For the educational purpose I am trying to reuse traits From and Into to implement traits FromCoercing and ToCoercing which allows coercive conversion, unlike original From and Into. For simplicity, I used primitive types, but in my version types are not primitive. I know there might be a better mechanism to reach the goal, but point to understanding how to reuse a system of traits to make them do what is required, not finding an optimal way of converting one thing into another coercively.

My code is:


fn main()
{
  let u32 : u32 = 13;
  let u64 : u64 = u32.into();
  println!( "u64 : {}", u64 );
  let u8 : u8 = u32 as u8;
  println!( "u8 : {}", u8 );
  let u8 : u8 = u32.into_coercing();
  println!( "u8 : {}", u8 );
}

//

pub trait FromCoercing< Src > : Sized
{
  fn from_coercing( _: Src ) -> Self;
}

impl< Src : Sized, FromSrc : From< Src > > FromCoercing< Src > for FromSrc
{
  fn from_coercing( src : Src ) -> Self
  {
    Self::from( src )
  }
}

impl FromCoercing< u32 > for u16
{
  fn from_coercing( src : u32 ) -> Self
  {
    src as u16
  }
}

//

pub trait IntoCoercing< Dst > : Sized
{
  fn into_coercing( self ) -> Dst;
}

impl< Src, Dst > IntoCoercing< Dst > for Src
where
  Dst : FromCoercing< Src >,
{
  fn into_coercing( self ) -> Dst
  {
    Dst::from_coercing( self )
  }
}

My understanding says impl< Src : Sized, FromSrc : From< Src > > FromCoercing< Src > for FromSrc does not conflict with impl FromCoercing< u32 > for u16, because there is no implementation of impl From< u32 > for u16. But compiler gives error:

error[E0119]: conflicting implementations of trait `FromCoercing<u32>` for type `u16`
  --> src/main.rs:28:1
   |
20 | impl< Src : Sized, FromSrc : From< Src > > FromCoercing< Src > for FromSrc
   | -------------------------------------------------------------------------- first implementation here
...
28 | impl FromCoercing< u32 > for u16
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `u16`
   |
   = note: upstream crates may add a new impl of trait `std::convert::From<u32>` for type `u16` in future versions

Where is the flaw in my logic and how to make the code working? Any suggestion to read?

Playground:

There isn't, but one might be defined by std, and then your code would break, merely because a 3rd-party crate (std) added a trait impl. Addition of trait impls is supposed to be a non-breaking change, so the compiler cautiously disallows this kind of impl.

As currently standing, what you want to do can't be achieved. Maybe the very-in-progress specialization feature on nightly could be used here, but I would advise strongly against it even if it did work, exactly because of this kind of ambiguity and lack of backward compatibility.

2 Likes

Hm..

How to make such reservation of space for the future ( as you mentioned made for std ) without actually making such implementation?

It is automatic.

Let's assume it's not possible to reach the goal by the way. What is your suggestion? How to implement a system of traits making the possible coercive conversion?

You can implement if for specific types manually. Not generically for all types implementing From. You can write a macro to avoid boilerplate code. If you want a trait hierarchy you can add your own alternative to From.

Other than that, unfortunately Rust doesn’t support your case as good as it probably could. While writing subtraits to existing traits form other crates is easy, adding something that’s effectively a supertrait is not always so straightforward.

It’s pretty likely that at some point in the future (yet it can still be a few years) you’ll get the ability to do what you’re trying to to in this example by telling the compiler: “In case there’s ever going to be a u16: From<u32> implementation, I still want my custom u16: FromCoercing<u32> implementation to take precedence”.


There’s another option currently that might help in some cases, mainly if your use case for FromCoercing/IntoCoercing is less in trait bounds in where clauses and more in ad-hoc usage. In this case, you could add an extra dummy parameter and two dummy/marker types, e.g. enum ViaFrom {} and enum Custom {}, and then use manual implementations

u16: FromCoercing<u32, Custom>

as well as a generic

impl<T, U> FromCoercing<U, ViaFrom> for T where T: From<U>

With ad-hoc usage of FromCoercing there’s then still the good change that type inference will be able to fill in the necessary dummy parameter so that u16::from_coercing(0_u32) and u32::from_coercing(0_u16) may both work without additional annotation.

Propagating these dummy arguments when appearing in function signatures or similar would however probably be quite annoying, hence my statement that this would work best when

your use case for FromCoercing/IntoCoercing is less in trait bounds in where clauses and more in ad-hoc usage

1 Like

Thank you @steffahn

Negative bound would help.

If you want to read up on things, there is

A negative bound that means "T doesn't implement the trait (today)" is too brittle, as it makes implementing the trait tomorrow a breaking change.

However we may eventually be able to declare we will never implement a trait for our type and then perhaps even have coherence take that into account.

1 Like

Thank you. Interesting reading.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.