Description
This is a tracking issue for attributes changing "Minimal Complete Definition" of a trait. "Minimal Complete Definition" is effectively a set of rules you need to follow (by implementing items of the trait) in order for a trait implementation to be "Complete" (and compile). Normally there is only one rule — implement all items that do not have defaults. However, sometimes it is meaningful to add additional restrictions.
As a simple example consider a trait with functions equal
and not_equal
, both of them can be implemented as !the_other_function(...)
, but only if the other one has a meaningful implementation. Thus, it may be beneficial for the library design to make Minimal Complete Definition be "you must implement at least one of equal
and not equal
".
A more realistic example would probably be performance related — one function is easier to implement, while the other theoretically allows a more performant implementation (see Read::{read, read_buf}
).
Current status
This is currently implemented as an internal unstable rustc attribute #[rustc_must_implement_one_of]
. It accepts a list of identifiers of trait items and adds a requirement that at least one of the items from the list is implemented. A usage example:
// `read` and `read_buf` are mutually recursive, it would be bad to implement neither
#[rustc_must_implement_one_of(read, read_buf)]
pub trait Read {
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
let mut buf = ReadBuf::new(buf);
self.read_buf(&mut buf)?;
Ok(buf.filled_len())
}
fn read_buf(&mut self, buf: &mut ReadBuf<'_>) -> Result<()> {
default_read_buf(|b| self.read(b), buf)
}
}
impl Read for Ty0 {}
//^ This will fail to compile even though all `Read` methods have default implementations
// Both of these will compile just fine
impl Read for Ty1 {
fn read(&mut self, buf: &mut [u8]) -> Result<usize> { /* ... */ }
}
impl Read for Ty2 {
fn read_buf(&mut self, buf: &mut ReadBuf<'_>) -> Result<()> { /* ... */ }
}
It is hopefully not the final form of the attribute, but just a "MVP". Some ideas that we might want to explore in the future:
- A way to specify multiple sets of items (i.e.
a
is mutually recursive withb
,c
is mutually recursive withd
, so MCD is(a | b) & (c | d)
) - Add support for non-function items (recursive constants? is this useful?)
- Consider if the trait-level attribute is a good solution
- Would something like item-level
#[requires(...)]
be easier to use/reason about?
- Would something like item-level
- Iterate on rustdoc output
- Consider how we can support more complicated cases like the
Iterator
, where a lot of methods can be implemented in terms of each other- Would it make sense to have multiple default implementations of an item and choose one of them depending on whatever another item is implemented?
- Write an RFC (!)
Stability
While the attribute itself is very unstable and "MVP", we can still use it in stable and unstable APIs, if we are sure that this is something we wish to support in the future. Thus, we need to be able to control the stability of default implementations (to be able to test if we even need to change MCD of a trait, for example).
For this we have 2 tools. We can either require an unstable method (#[rustc_must_implement_one_of(something_stable, something_unstable)]
, we can add a default implementation to something_stable
without worrying — to use the default implementation one would need to opt-in into unstable feature to implement something_unstable
), or use the #[rustc_default_body_unstable]
attribute.
#[rustc_default_body_unstable]
attribute, as with any other stability attributes, allows to set a feature gate to using a default body. For example:
// in std
pub trait Trait {
#[rustc_default_body_unstable(feature = "feat", isssue = "none")]
fn item() {}
}
// in a user crate
impl Trait for Type {} // <-- does not provide an `item` implementation, so it uses the default one
//~^ error: not all trait items implemented, missing: `item`
//~| note: default implementation of `item` is unstable
//~| note: use of unstable library feature 'feat'
//~| help: add `#![feature(constant_default_body)]` to the crate attributes to enable
// this is fine
impl Trait for AnotherType {
fn item() { println!("hehe"); }
}
About tracking issues
Tracking issues are used to record the overall progress of implementation.
They are also used as hubs connecting to other relevant issues, e.g., bugs or open design questions.
A tracking issue is however not meant for large scale discussion, questions, or bug reports about a feature.
Instead, open a dedicated issue for the specific matter and add the relevant feature gate label.
Steps
- Implement an MVP
- Write an RFC
- Implement the RFC
- Adjust documentation (see instructions on rustc-dev-guide)
- Stabilization PR (see instructions on rustc-dev-guide)
Unresolved Questions
See the "Current status".
Implementation history
- Implement
#[rustc_must_implement_one_of]
attribute #92164 - Check that
#[rustc_must_implement_one_of]
is applied to a trait #93386 - Implement
#[rustc_default_body_unstable]
#96478 - rustdoc: Add support for
#[rustc_must_implement_one_of]
#99235 - Tweak
rustc_must_implement_one_of
diagnostic output #105506