Skip to content

E0277: Unhelpful error message is given when indirect constraints cause blanket implementations to not get implemented #134346

@soareschen

Description

@soareschen

Code

// The following code demonstrates an incomplete error message returned from
// the Rust compiler, when there are unsatisfied constraints present in
// code that makes use of _context-generic programming_, as described at
// https://patterns.contextgeneric.dev/.
//
// More details about similar error is described here:
// https://patterns.contextgeneric.dev/debugging-techniques.html


// The trait to link a consumer trait with a provide trait, as explained by:
// https://patterns.contextgeneric.dev/consumer-provider-link.html#blanket-consumer-trait-implementation
pub trait HasComponents {
    type Components;
}

// The trait to delegate a provider implementation to another provider, as explained by:
// https://patterns.contextgeneric.dev/provider-delegation.html#blanket-provider-implementation
pub trait DelegateComponent<Name> {
    type Delegate;
}

// The name of out example component
pub struct StringFormatterComponent;

// Our example consumer trait, with the context type being the implicit `Self` type
pub trait CanFormatToString {
    fn format_to_string(&self) -> String;
}

// Our example provider trait, with the context type being the explicit `Context` type
pub trait StringFormatter<Context> {
    fn format_to_string(context: &Context) -> String;
}

// A blanket implementation that links the consumer `CanFormatToString` with
// the provider `StringFormatter`, using `HasComponents`
impl<Context> CanFormatToString for Context
where
    Context: HasComponents,
    Context::Components: StringFormatter<Context>,
{
    fn format_to_string(&self) -> String {
        Context::Components::format_to_string(self)
    }
}

// A blanket implementation that links the provider implementation of
// `StringFormatter` with another provider, using `DelegateComponent`.
impl<Component, Context> StringFormatter<Context> for Component
where
    Component: DelegateComponent<StringFormatterComponent>,
    Component::Delegate: StringFormatter<Context>,
{
    fn format_to_string(context: &Context) -> String {
        Component::Delegate::format_to_string(context)
    }
}

// An example provider for `StringFormatter`, which has a generic
// implementation that requires `Context: debug`
pub struct FormatWithDebug;

impl<Context> StringFormatter<Context> for FormatWithDebug
where
    Context: core::fmt::Debug,
{
    fn format_to_string(context: &Context) -> String {
        format!("{:?}", context)
    }
}

// An example concrete context.
// Note: we pretend to forgot to derive `Debug` to cause error to be raised.
// FIXME: Uncomment the line below to fix the error.
// #[derive(Debug)]
pub struct Person {
    pub first_name: String,
    pub last_name: String,
}

// The components tied to the `Person` context
pub struct PersonComponents;

// Implement the consumer traits for `Person` using
// the aggregated provider `PersonComponents`
impl HasComponents for Person {
    type Components = PersonComponents;
}

// Implement `PersonComponents: StringFormatter<Person>` using `FormatWithDebug`
impl DelegateComponent<StringFormatterComponent> for PersonComponents {
    type Delegate = FormatWithDebug;
}

// Checks that `Person` implements `CanFormatToString`
pub trait CanUsePerson: CanFormatToString {}

// This should raise an error, since we didn't implement `Debug` for `Person`
impl CanUsePerson for Person {}

Current output

error[E0277]: the trait bound `FormatWithDebug: StringFormatter<Person>` is not satisfied
  --> lib.rs:99:23
   |
99 | impl CanUsePerson for Person {}
   |                       ^^^^^^ the trait `StringFormatter<Person>` is not implemented for `FormatWithDebug`, which is required by `Person: CanFormatToString`
   |
   = help: the trait `StringFormatter<Context>` is implemented for `FormatWithDebug`
note: required for `PersonComponents` to implement `StringFormatter<Person>`
  --> lib.rs:49:26
   |
49 | impl<Component, Context> StringFormatter<Context> for Component
   |                          ^^^^^^^^^^^^^^^^^^^^^^^^     ^^^^^^^^^
...
52 |     Component::Delegate: StringFormatter<Context>,
   |                          ------------------------ unsatisfied trait bound introduced here
note: required for `Person` to implement `CanFormatToString`
  --> lib.rs:37:15
   |
37 | impl<Context> CanFormatToString for Context
   |               ^^^^^^^^^^^^^^^^^     ^^^^^^^
...
40 |     Context::Components: StringFormatter<Context>,
   |                          ------------------------ unsatisfied trait bound introduced here
note: required by a bound in `CanUsePerson`
  --> lib.rs:96:25
   |
96 | pub trait CanUsePerson: CanFormatToString {}
   |                         ^^^^^^^^^^^^^^^^^ required by this bound in `CanUsePerson`

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0277`.

Desired output

error[E0277]: the trait bound `FormatWithDebug: StringFormatter<Person>` is not satisfied
  --> lib.rs:99:23
   |
99 | impl CanUsePerson for Person {}
   |                       ^^^^^^ the trait `StringFormatter<Person>` is not implemented for `FormatWithDebug`
   |
   = help: the following constraint is not satisfied: `Person: Debug`
   = help: the trait `StringFormatter<Context>` is implemented for `FormatWithDebug`
note: required for `PersonComponents` to implement `StringFormatter<Person>`
  --> lib.rs:49:26
   |
49 | impl<Component, Context> StringFormatter<Context> for Component
   |                          ^^^^^^^^^^^^^^^^^^^^^^^^     ^^^^^^^^^
...
52 |     Component::Delegate: StringFormatter<Context>,
   |                          ------------------------ unsatisfied trait bound introduced here
note: required for `Person` to implement `CanFormatToString`
  --> lib.rs:37:15
   |
37 | impl<Context> CanFormatToString for Context
   |               ^^^^^^^^^^^^^^^^^     ^^^^^^^
...
40 |     Context::Components: StringFormatter<Context>,
   |                          ------------------------ unsatisfied trait bound introduced here
note: required by a bound in `CanUsePerson`
  --> lib.rs:96:25
   |
96 | pub trait CanUsePerson: CanFormatToString {}
   |                         ^^^^^^^^^^^^^^^^^ required by this bound in `CanUsePerson`

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0277`.

Rationale and extra context

Summary

When resolving Rust constraints that involve blanket implementations, Rust does not produce helpful error messages when missing indirect constraints caused the blanket implementation to not get implemented.

Rationale

The lack of informative error messages presents challenges in a new project that I am working on, context-generic programming, which implements a modular component system for Rust by making extensive use of blanket implementations. More details about how this error arised is described in the chapter of my book for debugging techniques.

For the purpose of this issue, I have attached a minimal code snippet with desugared code, so that it can be tested without importing my library cgp. For simplicity, the example code may look silly and does not demonstrate why it is written that way. The main purpose of the example code is to reproduce an example error message with as little code as possible.

Other cases

Rust Version

rustc 1.83.0 (90b35a623 2024-11-26)
binary: rustc
commit-hash: 90b35a6239c3d8bdabc530a6a0816f7ff89a0aaf
commit-date: 2024-11-26
host: aarch64-unknown-linux-gnu
release: 1.83.0
LLVM version: 19.1.1

Anything else?

The example code is also available on Rust Playground.

I already have a fix for this issue, and it is now available at #134348.

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-diagnosticsArea: Messages for errors, warnings, and lintsT-compilerRelevant to the compiler team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions