R
RUSTY YELLOW PAGES

#[diagnostic::on_unimplemented] / #[diagnostic::do_not_recommend]Attribute


Explanation

Both attributes live under the diagnostic tool-attribute namespace (written #[diagnostic::name(...)], a path with a :: in it rather than a single identifier — see #[meta] / #![meta] for attribute paths in general) and exist purely to make the compiler's error messages more helpful. Neither one changes what compiles, what type-checks, or how a program behaves — a crate that removed every #[diagnostic::...] attribute from its source would compile to identical code; only the wording of certain error messages would get worse.

#[diagnostic::on_unimplemented(message = "...", label = "...")], placed on a trait definition, customizes the error the compiler shows when some type fails to implement that trait where the trait is required — replacing (or supplementing) the generic "the trait bound T: SomeTrait is not satisfied" message with wording specific to that trait. The standard library uses this on several of its own most commonly-hit traits — Iterator, Fn/FnMut/FnOnce — which is why forgetting to implement Iterator correctly, or passing something that isn't callable where a closure is expected, tends to produce an error message that names the actual problem in plain language instead of the raw, generic trait-bound wording. message replaces the top-line error text; label customizes the inline caret annotation shown under the offending code span.

#[diagnostic::do_not_recommend], placed on an impl block (most often a blanket impl), tells the compiler not to suggest that impl as a fix when some other trait bound fails to hold nearby — even though the impl technically does apply. This matters specifically for blanket impls: a broad impl<T: SomeBound> Trait for T can technically satisfy a compiler's "here's an impl that would work" suggestion machinery in cases where mentioning it would only confuse the reader (the real fix is elsewhere, and the blanket impl is incidental, not the intended solution). Suppressing it from suggestions doesn't stop the impl from existing or working — code that already relies on it continues to compile exactly as before; only the compiler's advice-generation ignores it as a candidate to recommend.

Usage examples

Customizing the error message for an unimplemented trait

#[diagnostic::on_unimplemented(
    message = "`{Self}` cannot be rendered to the screen",
    label = "missing `Renderable` implementation"
)]
trait Renderable {
    fn render(&self) -> String;
}

Designing a public API

A UI framework's core trait is implemented by many types across an application; a plain, generic trait-bound error doesn't tell a newcomer what is missing or why — a custom on_unimplemented message speaks directly to the framework's own vocabulary instead.

#[diagnostic::on_unimplemented(
    message = "`{Self}` is not a `Widget` — implement `Widget` to use it in a layout",
    label = "not a `Widget`"
)]
trait Widget {
    fn measure(&self) -> (u32, u32);
}

struct Label {
    text: String,
}

impl Widget for Label {
    fn measure(&self) -> (u32, u32) {
        (self.text.len() as u32 * 8, 16)
    }
}

fn add_to_layout<W: Widget>(_widget: W) {}

fn build_layout() {
    add_to_layout(Label { text: "Hello".to_string() }); // <- compiles: Label implements Widget
    // add_to_layout(42); // would show the custom message above instead of a generic trait-bound error
}

A generic "the trait bound i32: Widget is not satisfied" tells a reader that something is wrong but not what to do about it, whereas a message written in the framework's own terms points directly at the fix; the Rust Reference documents this attribute as intended for exactly this kind of domain-specific guidance on commonly-hit trait bounds, following the same pattern the standard library uses on Iterator and the Fn traits.

Implementing traits

A serialization trait has both specific, hand-written impls for certain types and a broad blanket impl covering anything convertible through Display — when a genuinely unrelated type fails to implement the trait, suggesting the blanket impl as "the fix" would be more confusing than helpful, since the real gap is that the type isn't Display either.

trait ToWireFormat {
    fn to_wire(&self) -> String;
}

#[diagnostic::do_not_recommend] // <- don't suggest this blanket impl as the fix for unrelated failures
impl<T: std::fmt::Display> ToWireFormat for T {
    fn to_wire(&self) -> String {
        self.to_string()
    }
}

Without do_not_recommend, a compiler error involving some type that implements neither Display nor ToWireFormat directly could suggest "implement ToWireFormat" by pointing at this blanket impl, which is technically accurate but unhelpful — the underlying gap is Display, not ToWireFormat; the Rust Reference documents this exact "blanket impl technically applies but isn't the useful thing to suggest" scenario as its intended use.

Explanation

Embedded support: Full

Both #[diagnostic::on_unimplemented] and #[diagnostic::do_not_recommend] are purely compile-time diagnostic metadata, read only by the compiler while producing an error message — they have no runtime behavior to differ in the first place, so there's no genuine embedded angle beyond confirming they work exactly the same way in #![no_std] code. A core-only trait that's central to a HAL's public API (something implemented by every GPIO pin type, say) benefits from a custom on_unimplemented message for exactly the same reason a hosted crate's trait would: a clearer error the moment someone reaches for a type that doesn't implement it.

Usage examples

A HAL trait with a custom message for unimplemented pin types

#[diagnostic::on_unimplemented(
    message = "`{Self}` is not configured as an output pin",
    label = "missing `OutputPin` implementation"
)]
trait OutputPin {
    fn set_high(&mut self);
}