rust-lang / rust-lang/rust

TAIT coherence checks don't ensure composability of crates

Open
#130,978 5 comments 1 reaction 1 assignee View on GitHub

@fmease is already working on this.

Since Oct 10, 2024.

A-coherence A-trait-system C-bug F-impl_trait_in_assoc_type F-type_alias_impl_trait T-types
Dominant language
Rust
Stars
119k
Forks
16.1k
PR merge metrics
PR metrics pending

Description

The coherence checks for trait implementations where the receiver type is an opaque type (defined with TAIT) are designed and/or implemented in a way that doesn’t uphold the principle of seamless composability of crates. It also doesn’t uphold current principles of what kind of trait implementation constitutes a breaking change.


Here’s a reproducing example (consisting of 3 crates):

crate A

[package]
name = "a"
edition = "2021"
pub trait MyFrom<T> {
    fn from(value: T) -> Self;
}

pub trait AsFoo {}

pub struct Foo;

impl AsFoo for Foo {}

crate B

[package]
name = "b"
edition = "2021"

[dependencies]
a = { path = "../a" }
use a::{AsFoo, MyFrom};

pub struct Wrapper<T>(pub T);

impl<T> MyFrom<T> for Wrapper<T> {
    fn from(value: T) -> Self {
        Wrapper(value)
    }
}

impl<T> AsFoo for Wrapper<T> {}

crate C

[package]
name = "c"
edition = "2021"

[dependencies]
a = { path = "../a" }
b = { path = "../b" }
#![feature(type_alias_impl_trait)]

use a::{AsFoo, Foo, MyFrom};
// use b; // <- uncomment for error

type Alias = impl AsFoo;

struct Local;

impl MyFrom<Local> for Alias {
    fn from(_: Local) -> Alias {
        Foo
    }
}

The above example involving 3 crates A, B, C; A is a dependency of B and C.

C compiles successfully with just A as a dependency.
If B is added as a dependency of C (and actually used, by uncommenting the use b;) then the following error appears:

error[E0119]: conflicting implementations of trait `MyFrom<Local>` for type `Wrapper<Local>`
  --> src/lib.rs:10:1
   |
10 | impl MyFrom<Local> for Alias {
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: conflicting implementation in crate `b`:
           - impl<T> MyFrom<T> for Wrapper<T>;

The error also does make sense: If we further change crate C, so that the defining use of Alias produces not a Foo but a b::Wrapper<Local>, then the impls of MyFrom will become actually overlapping, even when the opaque type is treated transparently. As far as I can tell, it seems that the goal of the error was to be conservative and ensure that changing the actual concrete choice of type behind the opaque TAIT-type should not introduce any new overlap errors later.

As a consequence, in my opinion this means that without the crate B, there should probably also be some kind of error here.

Some more thoughts and observations:

  • the problematic impl<T> MyFrom<T> for Wrapper<T> is a blanket impl. Addition of such an impl is actually considered a breaking change in other kinds of situations
    • when such an impl is added for a type Wrapper<T> that already exists in previous versions, that’s considered technically breaking
    • however, if it’s introduced together with the type Wrapper<T>, that it okay; and it’s exactly this combination that “add trait B as dependency” achieves
  • the problematic impl<T> MyFrom<T> for Wrapper<T> produces the same error if crates A and B are combined into one. I split them up, because it makes an even stronger argument; nonetheless, common “what’s considered breaking” standards imply that crate A should of course also be allowed to simply add such a pair of struct Wrapper<T> + this impl of MyFrom, and this issue violates that principle, too.
  • I first noticed this issue in this code example, where some trait impls involving local traits and types, as well as From and AsRef<str> results in an error mention that surprisingly mentions random stuff such as gimli::common::DebugFrameOffset [presumably because it’s the first type the compiler finds that has some kind of From<T> for Self<T>-style implementation]. If nothing else that’s a surprising diagnostic, but unsurprisingly is just highlighted this more abstract&general issue with coherence-checks.
  • I have not tried reasoning more deeply about what kind of “orphan rules” a TAIT needs to fulfill to avoid this issue completely, but I wouldn’t be surprised if there are some straightforward ways of giving TAITs a more restrictive version of the orphan rules, which solve this issue.

@rustbot label +F-type_alias_impl_trait +A-coherence +A-traits +T-types

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.