Rust 1.78 improves explanations at trait and unsafe boundaries
Trait authors can add diagnostic hints, while debug builds execute selected standard-library precondition checks. Both changes make an existing contract easier to understand when it fails.
A missing trait implementation can be obvious to the compiler while remaining confusing to the person calling a library. An unsafe operation can also violate its contract without failing near the offending call. Rust 1.78, released on May 2, improves both situations through library-defined diagnostic hints and more accessible standard-library precondition assertions.
The changes act at different times. A trait diagnostic explains a compile-time failure. A precondition assertion can terminate an invalid execution in a debug build. Neither changes which trait implementation exists or which unsafe operation is valid. They change how clearly a violation can be observed.
I appreciate Rust's precision, but a precise rule is easier to use when its failure explains the abstraction involved. These additions let library authors communicate some of that meaning without requiring the caller to understand every internal type bound or wait for a distant consequence of undefined behavior.
Trait authors can explain the required behavior
The new diagnostic attribute namespace lets code provide hints to compiler diagnostics. Its first supported attribute, diagnostic::on_unimplemented, belongs on a trait. When a bound requires that trait and a type does not implement it, the compiler can incorporate the supplied message, label, and notes.
The release announcement shows how placeholders such as the trait's type parameters and Self can make the text specific to the failed obligation. A useful message can therefore connect the caller's type to the library concept it is missing. The goal is a better explanation than mechanically restating that an implementation is absent.
The standard-library example concerns Sized. A caller benefits from hearing that a type's size cannot be known at compile time, because that explains the property the trait expresses. Merely repeating the trait name would require the caller to make that connection independently.
This does not let a library replace type checking with its own interpretation. The compiler still identifies the unsatisfied bound. In the announcement's example, the custom text fills the main diagnostic and label, while the ordinary trait explanation remains available as help. The exact presentation is not a stable formatting contract.
Diagnostic hints can be ignored
The namespace's compatibility design is deliberate. The compiler is not required to use its hints, and an unrecognized diagnostic attribute is not an error. That permits a library to provide information that some compiler versions or implementations do not understand.
This is different from an attribute that changes a program's semantics. Ignoring a layout or calling-convention requirement could change meaning. Ignoring a diagnostic hint leaves the same underlying type error. The library's source can therefore retain useful explanatory metadata without making every consumer's compiler support that exact presentation feature.
The distinction also constrains how library authors should use it. Required application behavior cannot depend on whether a particular note appears. The note explains a failed contract, while documentation and types continue defining the contract itself.
For a public API, that is a useful division. Authors can improve a common error at the point where they understand the abstraction. They do not need to expose internal implementation details merely to make the default compiler message happen to read better.
Distributed release libraries hid existing assertions
The unsafe-precondition change addresses a different gap between implementation and developer experience. The standard library already contained assertions for selected unsafe requirements. Historically, those checks depended on the standard library itself being built with debug assertions.
Most developers receive a standard library compiled in release mode. Their own debug or test builds therefore did not normally run those assertions. Selecting a debug build for an application was insufficient to enable checks that had already been omitted from its precompiled dependency.
Rust 1.78 delays the condition until code generation so the user's debug-assertion setting controls the checks. Debug and test builds enable them by default. This changes whether an existing check runs without requiring each developer to rebuild the distributed library specially.
The mechanism matters because the package name alone does not identify where a build-time decision takes effect. Previously, the library distributor's build mode was decisive. The new arrangement brings the check into the application's selected debugging configuration.
A slice constructor checks only part of its contract
The announcement illustrates the change with a misaligned pointer passed to slice::from_raw_parts. The 1.78 implementation and safety documentation make the distinction between checked conditions and the full contract explicit.
The function takes a pointer and an element count. Its assertion checks alignment, non-nullness, and whether the total allocation size fits the permitted bound. The documentation also requires valid initialized elements in one allocation and imposes lifetime and mutation restrictions. Those requirements cannot all be established by the displayed alignment-and-size test.
Even an empty slice requires an aligned non-null pointer under the documented contract. The source explains that reference layout optimizations can depend on those properties. A zero element count therefore does not remove every pointer requirement.
The documentation's example of adjacent allocations is also useful. Two memory regions can happen to occupy consecutive addresses without belonging to one allocation. Combining them into a slice violates the constructor's contract even if arithmetic makes their addresses appear contiguous.
The new checks help expose selected violations. Passing the assertion is not evidence that every other requirement holds. The release announcement explicitly says the amount checked is not generally stable, so an application cannot use this as a complete runtime validator for arbitrary pointers.
Failure becomes closer to the invalid operation
The demonstrated failure is a non-unwinding panic followed by an abort. That differs from relying on an ordinary recoverable validation error. The operation already has undefined behavior when its caller violates the contract. The assertion supplies a diagnostic failure for a case the implementation can recognize.
I consider that proximity valuable when learning or reviewing unsafe code. An alignment failure reported at slice construction is more actionable than a later symptom elsewhere in the program. The safety argument still has to establish allocation validity, initialization, and lifetime before the call.
Trait hints and runtime precondition checks improve different parts of that reasoning process. The former explain an obligation the compiler cannot satisfy. The latter expose some invalid executions near a library boundary. Rust 1.78 makes those existing contracts easier to inspect while leaving their definitions in the type system and safety documentation.