Skip to content

Conversation

dingxiangfei2009
Copy link

@dingxiangfei2009 dingxiangfei2009 commented Aug 26, 2025

We would like to propose auto impl syntax for supertraits for a few language enhancement, to ultimately facilitate easier trait evolution and refactoring and easier trait authoring when trait hierarchy is in consideration.

24/09/2025: revision published with typo fixes and dropping the unsafe qualifier for probe_extern_impl attribute.

Past edit history 21/09/2025: revision published with the following changes. - Address [comments from @cramertj](https://github.com//pull/3851#discussion_r2301552062) by including the worked examples mentioned in the [design meeting](https://hackmd.io/@1n2nRkvSTd-QgK8cCPds1w/HkBmeCE7xx). A new proposal is included so that an unsafe attribute may be used to allow compiler to probe further and decide if `auto impl` default implementation would be conflicting. This might need consultation with the types team as its effectiveness and stability depends very much on the capabilities of the trait solver. - Address [comments from @ElchananHaas](https://github.com//pull/3851#discussion_r2306004253) and [comments from @N4tus](https://github.com//pull/3851#issuecomment-3239334843) on several deficiencies in the worked examples, such as missing superbounds and complexity in the motivating examples. - Address [a comment from @programmerjake](https://github.com//pull/3851#discussion_r2302020617) by removing the template text.

Rendered

@ehuss ehuss added T-lang Relevant to the language team, which will review and decide on the RFC. T-types Relevant to the types team, which will review and decide on the RFC. labels Aug 26, 2025

Impl blocks using auto implementations are simply a short-hand for multiple impl blocks with all of the consequences that implies.

When a trait has an `auto impl` entry, all impl blocks for the trait that do not use `extern impl` to opt-out of the auto implementation become equivalent to two impl blocks, one for the sub-trait and one for the super-trait. They are generated according to these rules:
Copy link
Member

@cramertj cramertj Aug 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When a trait has an auto impl entry, all impl blocks for the trait that do not use extern impl to opt-out of the auto implementation become equivalent to two impl blocks, one for the sub-trait and one for the super-trait.

AFAICT, this rule makes it backwards-incompatible to add auto impls for existing traits with existing supertraits, correct? That is, we would not be able to add an auto impl PartialOrd { ... } in Ord without breaking existing users, as existing impls would have to have specified extern impl PartialOrd;. I think it would be helpful for the RFC to step through some of the example use-cases (the ones discussed in the lang design meeting) and describe how they would look under this RFC.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have updated the document to address this.

We can explore the possibility of introducing relaxation so that for some cases the compiler can check further if auto impl may overlap and bail out by assuming an implicit extern impl statement.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am also adding the worked example on how the motivating examples from the lang design meeting would work under this RFC in the motivation section.

# Unresolved questions
[unresolved-questions]: #unresolved-questions

- What parts of the design do you expect to resolve through the RFC process before this gets merged?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the idea is for you as the RFC writer to answer these questions by writing your own list of questions instead of these, if you have none then you can just write "None so far." or something.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Understood. This was from the template text. It should have been dropped.

@traviscross traviscross added the I-lang-radar Items that are on lang's radar and will need eventual work or consideration. label Aug 27, 2025
@N4tus
Copy link

N4tus commented Aug 30, 2025

The explanation states that the traits need to have a super/sub trait relation. But some examples use traits e.g. MouseEventHandler and Double, that are not in a relation to the trait they provide an auto impl for.

The reason for this is that given that `trait Subtrait` has already provided its implementation, an implementation of `Subtrait` must choose between the default implementation and a user-defined implementation. We prefer explicit confirmation through `extern impl` declaration from the implementor, rather than making the compiler to reason about whether `auto impl` should be backfilled for ease of language feature implementation.

#### Extension: Possible relaxation through an unsafe attribute and a future-compatibility lint
For important ecosystem traits like `PartialOrd` and `Ord`, this rule is still unsatisfactory due to [the potential rewrites required](#po-o) on downstream crates, even though it could be as small as an additional `extern impl PartialOrd`. As an extension, the rule could be relaxed with an unsafe attribute `#![unsafe(probe_extern_impl)]` and apply further trait selection to decide whether the default implementation given by the `auto impl` block should be used.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what operation is this performing that could produce UB therefore requiring unsafe? if there is none, unsafe should not be used.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I will drop the unsafe qualifier.

@fmease
Copy link
Member

fmease commented Sep 22, 2025

@dingxiangfei2009 Please don't force-push in this repo, that's heavily discouraged. From the README.md:

Specifically, do not squash or rebase commits after they are visible on the pull request.

@dingxiangfei2009
Copy link
Author

Quick question for @fmease, should I rename the RFC file from serial 0000-... to 3851-... in this text by ourselves? Or will there be a bot to rename the file?

@tomassedovic
Copy link
Contributor

@dingxiangfei2009 the name won't get updated automatically. We've merged the 2025H2 RFC, forgot to update and had to open a separate PR to fix the name: #3860.

```
The reason for this is that given that `trait Subtrait` has already provided its implementation, an implementation of `Subtrait` must choose between the default implementation and a user-defined implementation. We prefer explicit confirmation through `extern impl` declaration from the implementor, rather than making the compiler to reason about whether `auto impl` should be backfilled for ease of language feature implementation.

#### Extension: Possible relaxation through an unsafe attribute and a future-compatibility lint
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you still have unsafe here

The reason for this is that given that `trait Subtrait` has already provided its implementation, an implementation of `Subtrait` must choose between the default implementation and a user-defined implementation. We prefer explicit confirmation through `extern impl` declaration from the implementor, rather than making the compiler to reason about whether `auto impl` should be backfilled for ease of language feature implementation.

#### Extension: Possible relaxation through an unsafe attribute and a future-compatibility lint
For important ecosystem traits like `PartialOrd` and `Ord`, this rule is still unsatisfactory due to [the potential rewrites required](#po-o) on downstream crates, even though it could be as small as an additional `extern impl PartialOrd`. As an extension, the rule could be relaxed with an unsafe attribute `#[probe_extern_impl]` and apply further trait selection to decide whether the default implementation given by the `auto impl` block should be used.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and here


In this scenario, any item in an impl block of the sub-trait using the ambiguous name will always refer to the item from the sub-trait. This means that the only way to override the item from the super trait is to use `extern impl`.

## Mandatory `external impl` declaration
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
## Mandatory `external impl` declaration
## Mandatory `extern impl` declaration

It's possible to declare that an auto implementation is unsafe.
```rs
trait MySubTrait: MyTrait {
unsafe auto impl MyTrait;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so, does this create an unsafe requirement or discharge an existing unsafe requirement? you seem to have it do both which is generally not how Rust uses unsafe anymore, now that we have unsafe_op_in_unsafe_fn enabled by default

Copy link
Author

@dingxiangfei2009 dingxiangfei2009 Sep 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see that this can be confusing.

  • Inside a trait Subtrait: .. { .. } block, unsafe auto impl creates an unsafe requirement.
  • Inside a impl Subtrait { .. } block, unsafe auto impl discharges the corresponding unsafe requirement.

Should we adjust the "keyword" orders, so that the distinction can be more obvious?

Copy link
Member

@programmerjake programmerjake Sep 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that seems less useful than what I would have expected needing unsafe for -- something like:

/// safety: `returns_even` must always return an even integer
// unsafe means we create a requirement that impls must fulfill
pub unsafe Supertrait {
    fn returns_even(&self) -> u8;
}

pub trait Subtrait: Supertrait {
    /// safety: returns_even always returns an even integer
    // unsafe here means we're fulfilling the requirement since this is an impl of Supertrait
    unsafe auto impl Supertrait {
        fn returns_even(&self) -> u8 {
            self.get_int().wrapping_mul(2)
        }
    };
    fn get_int(&self) -> u8;
}

impl Subtrait for u8 {
    fn get_int(&self) -> u8 {
        *self
    }
}

pub struct Even(u8);

impl Subtrait for Even {
    // no unsafe needed here because this isn't fulfilling or creating any safety requirements,
    // the unsafe impl Supertrait below does that,
    // all we're doing here is saying this impl doesn't also impl Supertrait
    extern impl Supertrait;
    fn get_int(&self) -> u8 {
        self.0 / 2
    }
}

// safety: returns_even always returns an even integer because Even contains an even integer
// unsafe here means we're fulfilling the requirement since this is an impl of Supertrait
unsafe impl Supertrait for Even {
    fn returns_even(&self) -> u8 {
        self.0
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
I-lang-radar Items that are on lang's radar and will need eventual work or consideration. T-lang Relevant to the language team, which will review and decide on the RFC. T-types Relevant to the types team, which will review and decide on the RFC.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants