February 8, 2024·4 min read·engineeringrustdiagnosticstypesabi

Rust 1.76 makes inferred type names available for diagnostics

type_name_of_val exposes a diagnostic name without requiring a written type parameter. Rust 1.76 also documents ABI compatibility and adds a specific guarantee for char and u32.

Rust can infer a type that is inconvenient or impossible to write at a call site. A closure has a compiler-created type, and an opaque iterator return hides its concrete type behind a trait bound. Those properties help APIs express behavior without exposing every implementation detail, but they can make debugging confusing. Rust 1.76, released on February 8, stabilizes std::any::type_name_of_val to obtain a descriptive type name from a reference.

The function fills a diagnostic gap. It does not introduce general runtime reflection or a persistent identifier for Rust types. I appreciate Rust's explicit type semantics, and this addition is useful precisely when inference has already done its job but a person needs to understand the concrete result.

A reference lets inference supply the type parameter

The release announcement contrasts the new function with type_name::<T>(), available since Rust 1.38. That older function requires a type argument. When the type is awkward to spell, asking the compiler to infer it from a borrowed value is easier than reproducing it manually.

The implementation and documentation at the 1.76 tag show that the new function delegates to type_name::<T>(). Its parameter is a reference to a possibly unsized T. The returned value is a static string slice describing that type.

The parameter supplies type information. The implementation does not inspect the value's fields to construct a description of its current state. Two values of the same inferred type reach the same type-name operation regardless of their contents. This is why it works for a closure or the concrete iterator returned through impl Iterator without becoming a value-serialization facility.

The announcement's iterator example returns an array iterator through an opaque return type. Calling the new function on a reference exposes the concrete iterator description for diagnostics. That does not change the function's declared return contract or give downstream code a stable name it can depend on.

The string is deliberately unsuitable as an identity

The documentation states that the exact output is unspecified and may change between compiler versions. It is a best-effort description. The underlying type-name documentation also says different types may have the same name and that some type information, such as lifetime specifiers, is omitted.

Those restrictions define useful boundaries for the feature. Printing a name in a debugging session asks a human to interpret a compiler-produced description. Using the same string as a database discriminator or wire-format tag would require stability and uniqueness the API does not promise.

The string's static lifetime does not change that contract. It describes how long the returned string can be borrowed in the running program. It does not promise that the same source compiled with another compiler release will produce the same bytes.

A diagnostic snapshot can therefore change even when the program's relevant semantics do not. That is an implication of the documented format freedom. Code that makes decisions from the text would turn a diagnostic detail into an application dependency.

Trait objects retain their erased view

The documentation also says the function does not resolve trait objects. Given a reference already coerced to a trait object, it can report a trait-object description instead of the original concrete type. Borrowing a value does not automatically preserve the concrete type through every later coercion.

This follows from the generic operation the implementation performs. It reports the type selected for T at the call. A trait-object reference selects the trait-object view. The function does not discover the hidden concrete implementation by traversing the object's dispatch machinery.

I find that limitation consistent with the API's purpose. It helps explain inferred types where the compiler still has the relevant type at the call site. It does not reverse an abstraction that the program has explicitly chosen to erase.

ABI documentation concerns a different compatibility question

Rust 1.76 also adds documentation about function-signature ABI compatibility. The release notes explain that most of the material records existing guarantees. The new guarantee is that char and u32 are ABI-compatible, extending their existing equal size and alignment to function-call behavior.

The function-pointer documentation in the release source explains why equal layout alone is insufficient. An integer and a floating-point value may occupy the same amount of memory but travel through different registers when passed to a function. Matching size and alignment does not establish a valid call through a differently typed function pointer.

The documentation also distinguishes ABI compatibility from value validity. A type can have an ABI-compatible representation while still restricting which values it permits. Calling through a compatible signature does not authorize passing a bit pattern invalid for the callee's declared type.

This matters when interpreting the char and u32 addition. It is a calling-convention guarantee, not permission to treat every integer as a valid character. The broader documentation is aimed at unusual low-level cases involving altered function-pointer types or external declarations. Ordinary direct calls checked by Rust's type system do not require callers to establish those relationships manually.

The two changes improve different kinds of explanation. type_name_of_val lets diagnostics describe an inferred type. ABI documentation states when low-level call representations are compatible. Keeping those contracts distinct makes both additions more useful, because neither a printed name nor a matching memory size is enough to establish that two types can be substituted in a call.