Implement `Into` for container if contained type implements `Into`

Hi everyone

I have a container type Spanned that wraps around some inner value of type T:

struct Span;
    
struct Spanned<T> {
    value: T,
    span: Span,
}

Now I would like to implement the Into trait on the container whenever the inner type also implements Into. My approach was the following:

impl<F,T> Into<Spanned<T>> for Spanned<F> where F: Into<T> {
    fn into(self) -> Spanned<T> {
        Spanned{value: self.value.into(), span: Span}
    }
}

However this fails with a compiler error telling me that there is already a conflicting implementation in the core crate. What's the reason that this does not work?

Thanks in advance!
Cheers,

Fabian

Congratulations, it sounds like you've encountered your first coherence issue! In this particular case there is already a broad impl in core which looks like impl<T, F> Into<T> for F where T: From<F> (or something along those lines). When the compiler sees your new impl there are now two overlapping ways to convert from a Spanned<F> into a Spanned<T>, and the compiler has no way of knowing which one to use.

Basically, what you've written is so general that it overlaps with other things and that means there is no longer a single way to turn one thing into another. I think specialization is one way of dealing with these kinds of issues. Otherwise you could introduce some intermediate trait like trait IntoSpanned<F> { ... }.

You may want to ask someone who works on the trait system if you want to find out more, I imagine someone like @nikomatsakis would be able to point you in the right direction.

1 Like

I don't know how to fix your compiler error but shouldn't this be:

impl<F,T> Into<Spanned<T>> for Spanned<F> where F: Into<T> {
  fn into(self) -> Spanned<T> {
    Spanned{value: self.value.into(), span: Span}
  }
}

or something similar?

You'll notice that Option doesn't have such an impl either.

Tweaking the coherence rules to allow this is very much desired (it came up in the Try trait RFC, for example), but hasn't yet happened.

1 Like

You're not supposed to implement Into. You should implement From instead. But implementing From also doesn't work for some reason (playground).

It’s the same reason: coherence. stdlib defines impl<T> From<T> for T. Take the impl you’re trying to implement in the playground and imagine the F type is the same as the T type - that then conflicts with the blanket impl in the stdlib.

This is where specialization could help, but blanket impls need to opt-in to allow themselves to be specialized.

Indeed, thanks. I fixed the original post.

Okay, so this is a coherence issue. I think I ran into this before when I tried to provide a blanket default impl for a trait, and then have some select specialized impls that override it. Apparently this is possible in nightly Rust (e.g. as used by scrapmetal), but I'm bound to the stable compiler unfortunately.

I'll follow the RFC progress on this issue, it's quite interesting. Thanks a lot for the many helpful replies!