How to avoid conflict with blanket trait impl?


#1

I have two crates, let’s call them bin and util. util provides the following traits:

pub trait Util {
    fn frob();
}

trait SpecialUtil {}

impl<U: SpecialUtil> Util for U { ... }

In bin, I have the following code:

pub struct Foo { ... }

impl Util for Foo { ... }

Rust gives me the following error:

error[E0119]: conflicting implementations of trait `util::Util` for type `bin::Foo`:
  --> lib.rs:3
   |
3  | impl Util for Foo {
   | ^^^^^^^^^^^^^^^^^^^
   |
   = note: conflicting implementation in crate `util`:
           - impl<U> util::Util for U
             where U: util::SpecialUtil;
   = note: downstream crates may implement trait `util::SpecialUtil` for type `bin::Foo`

I don’t understand how downstream crates could implement util::SpecialUtil for Foo. Wouldn’t that violate coherence since those crate neither define the trait (as util does) nor define the type (as bin does)?


#2

I cannot reproduce this. Script that should reproduce this based on what you wrote but doesn’t:

#!/bin/sh

cargo new --lib util
cargo new --bin bin

cat >util/src/lib.rs <<'REPRO'
pub trait Util {
    fn frob();
}

pub trait SpecialUtil {}

impl<U: SpecialUtil> Util for U {
    fn frob() {
        unimplemented!()
    }
}
REPRO

cat >>bin/Cargo.toml <<'REPRO'
util = { path = "../util" }
REPRO

cat >bin/src/main.rs <<'REPRO'
use util::Util;

pub struct Foo {
    /* ... */
}

impl Util for Foo {
    fn frob() {
        unimplemented!()
    }
}

fn main() {}
REPRO

cargo build --manifest-path bin/Cargo.toml

#3

Hmm maybe it’s an issue with my exact setup. Here’s an un-simplified copy+paste of the actual traits. Maybe I missed some subtle detail in that simplification.

Crate packet:

use zerocopy::{ByteSlice, ByteSliceMut};

pub trait BufferView { ... }
pub struct ParseMetadata { ... }

pub trait ParsablePacket<B: ByteSlice, ParseArgs>: Sized {
    type Error;

    fn parse<BV: BufferView<B>>(buffer: BV, args: ParseArgs) -> Result<Self, Self::Error>;

    fn parse_mut<BV: BufferViewMut<B>>(buffer: BV, args: ParseArgs) -> Result<Self, Self::Error>
    where
        B: ByteSliceMut,
    { ... }

    fn header_len(&self) -> usize;
    fn body_len(&self) -> usize;
    fn footer_len(&self) -> usize;

    fn parse_metadata(&self) -> ParseMetadata { ... }
}

pub trait ParsableInnerPacket<B: ByteSlice, ParseArgs>: Sized {
    type Error;

    fn parse<BV: BufferView<B>>(buffer: BV, args: ParseArgs) -> Result<(Self, BV), Self::Error>;

    fn parse_mut<BV: BufferView<B>>(buffer: BV, args: ParseArgs) -> Result<(Self, BV), Self::Error>
    where
        B: ByteSliceMut,
    { ... }

    fn len(&self) -> usize;
}

impl<B: ByteSlice, ParseArgs, P: ParsableInnerPacket<B, ParseArgs>> ParsablePacket<B, ParseArgs>
    for P
{
    type Error = P::Error;

    fn parse<BV: BufferView<B>>(buffer: BV, args: ParseArgs) -> Result<Self, Self::Error> { ... }

    fn header_len(&self) -> usize { ... }
    fn body_len(&self) -> usize { ... }
    fn footer_len(&self) -> usize { ... }
}

Then, crate recovery_netstack_core:

use zerocopy::ByteSlice;
use packet::ParsablePacket;

pub struct EthernetFrame<B> { ... }

impl<B: ByteSlice> ParsablePacket<B, ()> for EthernetFrame<B> {
    type Error = ParseError;

    fn header_len(&self) -> usize { ... }
    fn body_len(&self) -> usize { ... }
    fn footer_len(&self) -> usize { ... }

    fn parse<BV: BufferView<B>>(mut buffer: BV, args: ()) -> Result<Self, ParseError> { ... }
}

The error:

error[E0119]: conflicting implementations of trait `packet::ParsablePacket<_, ()>` for type `wire::ethernet::EthernetFrame<_>`:
  --> core/src/wire/ethernet.rs:60:1
   |
60 | impl<B: ByteSlice> ParsablePacket<B, ()> for EthernetFrame<B> {
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: conflicting implementation in crate `packet`:
           - impl<B, ParseArgs, P> packet::ParsablePacket<B, ParseArgs> for P
             where B: zerocopy::ByteSlice, P: packet::ParsableInnerPacket<B, ParseArgs>;
   = note: downstream crates may implement trait `packet::ParsableInnerPacket<_, ()>` for type `wire::ethernet::EthernetFrame<_>`

#4

In short, I believe it’s because you have generic type parameters - downstream crate can impl that trait for EthernetFrame<TheirType> with their own types plugged in for the trait as well.

You might find https://github.com/sgrif/rfcs/blob/sg-re-re-balancing-coherence/text/0000-re-rebalancing-coherence.md useful, particularly the first part of the “Teaching Users” section.


#5

OK, I think I have a somewhat shaky understanding of the issue, but not enough to know how to fix it. Are there any solutions that you can recommend? Preferably that don’t require fundamental changes to the types/traits involved, but if necessary, so be it.


#6

One option might be to put a dummy “tag” type into, say, EthernetFrame - so the impl would be for EthernetFrame<YourTagType, B>; the tag, as the name suggests, doesn’t serve any purpose other than to close the coherence issue.

Beyond that, it’s a bit hard to say without understanding the purpose of the blanket impls you have and also how the types are intended to be used.


#7

OK, I think I figured out how I’m going to deal with this. I’m going to make two traits - ParsablePacket and ParsableInnerPacket, but instead of operating directly on ParsablePacket, I’m going to make a third trait, Parsable, with blanket impls from both of the first two traits. That way you’ll never implement a trait which also has a blanket impl.

Out of curiosity, do you think the RFC you linked to would make the old version of my code work? From my understanding of the RFC, I don’t think it would.


#8

Yeah, I don’t think it would - I only linked to it because it’s the best description of coherence/orphan rules that I’ve seen :slight_smile:.