Following on the discussion around this compiler-MCP I've wrote a first draft of an RFC to provide a #[diagnostic::on_unimplemented]
attribute for general usage. This attribute is supposed to allow crate authors to hint the compiler to emit specific error messages. Along with the specific attribute this RFC suggests to add a namespace with a set of common rules. This hopefully makes it easier to add similar attributes later on.
Summary
This RFC proposed to add a stable #[diagnostic]
attribute namespace, which contains attributes to influence error messages emitted by the compiler. In addition it proposed to add a #[diagnostic::on_unimplemented]
attribute to influence error messages emitted by unsatisfied traits bounds.
Motivation
Rust has the reputation to generate helpful error messages if something goes wrong. Nevertheless there are always cases of error messages which can be improved. One common example of such error messages in the rust ecosystem are those that are generated by crates using the type system to verify certain invariants at compile time. While these crates provide additional guarantees about invariants, they sometimes generate large error messages if something goes wrong. These error messages do not always indicate clearly what went wrong. Well known examples of crates with such issues include bevy, axum or diesel. Giving authors of such crates tools to control the error messages emited by the compiler would allow them to improve the situation on their own.
Guide-level explanation
This feature has two possible groups of users:
- Users that develop code and consume error messages from the compiler
- Users that write crates involving complex type hierarchies
The first user group will interact with the proposed feature through the error messages emitted by the compiler. As of this I do not expect any major documentation requirements for this group of users. Although we might want to indicate that a certain error message was provided via the described feature set, rather than by the compiler itself to prevent users for filling issues about bad error messages in the compilers issue tracker.
The second user group interacts with the described feature through attributes. These allow them to hint the compiler to emit specific error messages in certain cases. The #[diagnostic]
attribute namespace provides a general framework for what can and can't be done by such an attribute. As of this users won't interact directly with the attribute namespace itself.
The #[diagnostic::on_unimplemented]
attribute allows to hint the compiler to emit a specific error message if a certain trait is not implemented. This attribute should provide the following interface:
#[diagnostic::on_unimplemented(
message="message",
label="label",
note="note"
)]
trait MyIterator<A> {
fn next(&mut self) -> A;
}
fn iterate_chars<I: MyIterator<char>>(i: I) {
// ...
}
fn main() {
iterate_chars(&[1, 2, 3][..]);
}
which might result in the compiler emitting the following error message:
error[E0277]: message
--> <anon>:14:5
|
14 | iterate_chars(&[1, 2, 3][..]);
| ^^^^^^^^^^^^^ label
|
= note: note
= help: the trait `MyIterator<char>` is not implemented for `&[{integer}]`
= note: required by `iterate_chars`
I expect the new attributes to be documented on the existing Diagnostics attributes page on the rust reference similar to existing attributes like for example #[deprecated]
Reference-level explanation
The #[diagnostic]
attribute namespace
This RFC proposes to introduce a new #[diagnostic]
attribute namespace. This namespace is supposed to contain different attributes, which allow users to hint the compiler to emit specific error messages in certain cases like type mismatches, unsatisfied trait bounds or similar situations. By collecting such attributes in a common namespace it is easier for users to find useful attributes and it is easier for the language team to establish a set of common rules for these attributes.
Any attribute in this namespace may:
- Hint the compiler to emit a specific error message in a specific situation
Any attribute in this namespace is not allowed to:
- Change the result of the compilation, which means applying such an attribute should never cause an error as long as they are syntactically valid
The compiler is allowed to:
- Ignore the provided hints
- Use only parts of the provided hints, for example for the proposed
#[diagnostic::on_unimplemented]
only use themessage
option - Change this behaviour at any time
The compiler must:
- Verify that the attribute is syntactically correct, which means:
- Its one of the accepted attributes
- It only contains the allowed options
- Any provided option follows the defined syntax
- Follow the described semantics if the attribute is not ignored.
The #[diagnostic::on_unimplemented]
attribute
This section describes the syntax of the on_unimplemented
attribute and additionally how it is supposed to work. Implementing the later part is optional as outlined in the previous section.
#[diagnostic::on_unimplemented(
message="message",
label="label",
note="note",
)]
trait MyIterator<A> {
fn next(&mut self) -> A;
}
Each of the options message
, label
and note
are optional. They are separated by comma. The tailing comma is optional. Specifying any of these options hints the compiler to replace the normally emitted part of the error message with the provided string. At least one of the options need to exist. The error message can include type information for the Self
type or any generic type by using {Self}
or {A}
(where A
refers to the generic type name in the definition).
#[diagnostic::on_unimplemented(
on(
_Self = std::string::String,
note = "That's only emitted if Self == std::string::String",
),
on(
A = String,
note = "That's only emitted if A == String",
),
on(
any(A = i32, _Self = i32),
note = "That's emitted if A or Self is a i32",
),
on(
all(A = i32, _Self = i32),
note = "That's emitted if A and Self is a i32",
),
message="message",
label="label",
note="That's emitted if neither of the condition above are meet",
)]
trait MyIterator<A> {
fn next(&mut self) -> A;
}
In addition the on()
option allows to filter for specific cases. It accepts a set of filter options. A filter option consists on the generic parameter name from the trait definition and a type path against which the parameter should be checked. These type path could either be a fully qualified path or refer to any type in the current scope. As a special generic parameter name _Self
is added to refer to the Self
type of the trait implementation. A filter option evaluates to true
if the corresponding generic paramater in the trait definition matches In addition it can contain one or more of the message
, label
or note
option set to customise the error message hint in these cases. It is possible to supply more than one on()
option per #[diagnostic::on_unimplemented]
attribute. They are evaluated in the supplied order. The first matching option provides the corresponding value for the message
/label
/note
options. If none of the on()
option matches the values
The any
and all
option allows to combine multiple filter options. The any
matches is one of the supplied filter options evaluates to true
, the all
option requires that all supplied filter options evaluate to true. These options can be nested to construct complex filters.
Drawbacks
A possible drawback is that this feature adds additional complexity to the compiler implementation. The compiler needs to handle an additional attribute namespace with at least one additional attribute.
Another drawback is that crates hint lower quality error messages than the compiler itself. Technically the compiler would be free to ignore such hints, practically I would assume that it is impossible to judge the quality of such error messages in an automated way.
Rationale and alternatives
This proposal tries to improve error messages generated by rustc. It would give crate authors a tool to influence what error message is emitted in a certain situation, as they might sometimes want to provide specific details on certain error conditions. Not implementing this proposal would result in the current status, where the compiler always shows a "general" error message, even if it would be helpful to show additional details.
Prior art
- rustc_on_unimplemented already provides the described functionality as rustc internal attribute. It is used for improving error messages for various standard library API's. This repo contains several examples on how this attribute can be used in external crates to improve their error messages.
- GHC provides an Haskell language extension for specifying custom compile time errors
Notably all of the listed similar features are univocal language extensions.
Unresolved questions
Clarify the procedure of various potential changes prior stabilisation of the attribute namespace:
- Process of adding a new attribute to the
#[diagnostics]
attribute macro namespace- Requires a RFC?
- Requires a language/compiler MCP? (My preferred variant)
- Just a PR to the compiler?
- Process of extending an existing attribute by adding more options
- Requires a RFC?
- Requires a language/compiler MCP?
- Just a PR to the compiler?
- Process of removing support for specific options or an entire attribute from rustc
- Requires a compiler MCP?
- Just a PR to the compiler?
Future possibilities
- More attributes like
#[diagnostics::on_type_error]
- Extend the
#[diagnostics::on_unimplemented]
attribute to incorporate the semantics of#[do_not_recommend]
- Un-RFC
#[do_not_recommend]
?