Rust 1.79 brings constant evaluation into generic expressions
Inline const blocks let expressions request compile-time evaluation using surrounding generic parameters. Associated-type bounds make related generic requirements easier to state.
A generic function may need a compile-time value involving its type parameter without needing a separately named constant. Before Rust 1.79, expressing that request could require an associated-constant workaround. The June 13 release stabilizes const { ... } in expression position, including access to in-scope generics and inference of the block's result type.
The change gives constant evaluation a local, explicit spelling. It is useful for repeated array initialization and other operations that need a constant result without adding a declaration solely to name it. The same release introduces associated-type bound syntax that removes another kind of generic boilerplate.
I find Rust's type system most useful when its syntax expresses a real requirement without requiring supporting declarations that obscure it. These additions reduce that distance. They keep compile-time evaluation and trait obligations explicit while removing some of the names previously needed just to state them.
Explicit constant evaluation avoids promotion rules
RFC 2920, dated April 2020, explains the original motivation. Named constants guarantee compile-time initialization. They can also be used in contexts where an ordinary runtime expression would not have the same behavior, including initializing arrays of values that do not implement Copy.
Implicit promotion already allowed some expressions to receive constant treatment. The RFC explains why extending promotion indefinitely is unattractive. Promotion must avoid surprising users by moving observable work out of runtime, so eligibility depends on detailed rules about the expression and its type.
An explicit block changes the user's request. The programmer deliberately enters constant evaluation. The compiler no longer has to infer that this would be an unobservable transformation of an ordinary runtime expression. A named declaration remains useful for a shared concept, but a one-off value can stay at its use site.
The RFC also discusses broader possibilities, including patterns. The June release stabilizes expression-position blocks. Historical proposal examples need to be read with that release boundary in mind. The announcement supplies the operative description of what is available now.
The generic array example is the practical difference
The release announcement demonstrates constructing an array of optional generic values. Its key expression is:
[const { None::<T> }; N]
Here T and the constant length N come from the surrounding generic function. Each array element is the absent value of Option<T>. The initializer does not require an arbitrary T to implement Copy merely because it repeats that absent value.
Without the inline constant, the announcement says this generic case previously needed a verbose trait declaration with an associated constant. The auxiliary trait supplied a place where the constant could refer to the type parameter. Inline const places that relationship directly inside the initializer.
Type inference is another part of the convenience. A named constant requires its type to be written in the declaration. An inline const can infer its type from the expression and surrounding context. The block remains a constant context even though its type is inferred.
The original RFC initially restricted access to surrounding generic parameters and described relaxing that restriction as a future goal. Rust 1.79's release description explicitly includes those parameters. That difference is a useful example of a design progressing beyond an early proposal limitation without requiring the user to reproduce the intervening workaround.
Associated-type bounds move the requirement to its subject
The new syntax for an associated item bound is T: Trait<Assoc: Bound>. In a where clause, the release notes describe it as equivalent to separate requirements that T implements Trait and its associated Assoc type implements Bound.
The requirement concerns a type chosen by a trait implementation. Adding an otherwise unnecessary generic parameter merely to name that associated type can make the signature longer and suggest the caller has another independent choice. The compact syntax ties the requirement to the associated type where it belongs.
In the where-clause case, existing separate bounds remain valid. The new form groups a trait requirement with the property of its associated type instead of replacing the underlying obligation.
An iterator provides a familiar example. An API may accept an iterator whose items are copyable without needing to name the item type separately. The release announcement gives impl Iterator<Item: Copy> as the opaque-return form. The return contract exposes the item capability while leaving its concrete type unnamed.
This is more than punctuation in every position. The announcement distinguishes a supertrait bound such as trait CopyIterator: Iterator<Item: Copy> from a where clause on the trait declaration. The bound in supertrait position is implied when the trait is used. Moving the condition to that position can therefore make the contract available where consumers need it.
The syntax also supports bounds on associated types of associated types. That lets a trait declaration constrain a nested projection without introducing another unrelated name. The useful change is that the written structure follows the dependency between the types.
A stronger guarantee is different from an inferred optimization
The distinction between an explicit request and an implementation convenience also appears in this release's alignment changes. pointer::align_offset now promises consistent runtime behavior based on the input instead of being allowed to return usize::MAX unconditionally. The sentinel still indicates that the requested alignment cannot be achieved.
Similarly, slice::align_to and its mutable counterpart now promise the largest possible aligned middle portion. Previously their documentation allowed a less useful result, including returning everything as the unaligned prefix. The release says those caveats largely concerned possible constant evaluation, although the methods are stable only for non-const use.
These are contractual improvements. A caller can write code relying on the stronger behavior without assuming the implementation merely happens to choose it. That is different from a compiler optimizing a calculation away while leaving its observable contract unchanged.
I appreciate the consistency across these changes. An inline const makes the evaluation context explicit, and an associated-type bound states the required property at the relevant type. The alignment methods also give callers stronger documented behavior. In each case, source code can express the actual requirement with less dependence on an indirect workaround or a permissive implementation detail.