Summary:
Add a mechanism allowing types to list traits that are intentionally not implemented, and report attempts to implement any of these traits for the type as an error.
Motivation:
There are cases where the invariants of types can only be upheld through code inspection, when the type system could help instead. It is very easy to derive some traits on types that intentionally do not implement those traits, especially in development environments with multiple contributors. This RFC proposes a mechanism to inform the compiler of this fact, such that attempting to implement the trait turns into a compile-time error.
An example of this is a safe wrapper around unsafe pointer types. It may not be safe to clone this type, but the compiler will not complain if #[derive(Clone)] is added to the type declaration. This can be easy to overlook during code review because deriving Clone and other fundamental traits is such a common operation. The safety of the code would be better preserved if the compiler knew the intention of the original author of the safe wrapper and could report the conflict between the new change and the existing code.
Detailed Design:
Extend the existing implementation of impl !Trait for T to support any trait, not just Send or Sync. This feature is currently scope as an opt-out mechanism for a default implementation, but adding the ability to opt-out of manual implementations seems like a logical extension without any extra cognitive overhead.
This means that the following should be allowed:
struct NotCloneable;
impl !Clone for NotCloneable {}
And the following should report an error:
#[derive(Clone)]
struct NotCloneable;
impl !Clone for NotCloneable {}
This only affects traits that can be imported by the crate that declares the type. The !Trait bound can only contain such traits, and those traits cannot be implemented by downstream crates because both the traits and the type are not local to such crates. As such, this does not represent any more risk for causing breaking changes in the ecosystem than removing an existing trait implementation if such negative bounds are added.
Drawbacks:
- The meaning of
!Trait changes slightly from “ignore any default definition” to “this trait is not allowed to be implemented”.
- The
!Trait syntax is not stabilized yet (optin_builtin_traits feature), and tying this less-controversial feature to it makes it harder to stabilize.
Alternatives:
- Add a new annotation to types like
#[never_impl(Trait, AnotherTrait)] which means the same thing but avoids changing the meaning of an existing feature.
- Add no new features; continue to rely on comments, code review, and compile-fail tests supported by the compiletest crate.
Unresolved questions:
None.