August 31, 2017·5 min read·engineeringrusttraitslanguage

Rust 1.20 puts constants in trait contracts

Associated constants let an implementation provide a fixed value through its trait. They remove the need to model constant data as ordinary functions merely to connect it to a type.

A trait describing numeric behavior may need a fixed value such as infinity. Before associated constants, it could expose a function returning that value, while a separate module supplied an actual constant for constant-expression uses. The same concept then appeared through two interfaces. Rust 1.20 lets the trait state that the item is a constant and lets each implementation supply its value.

The August 31 announcement presents associated constants for structures, enums, and traits. It traces the design to RFC 195, proposed in August 2014. That RFC covers several kinds of associated item, whose implementation and stabilization have proceeded at different times. This release delivers the constants part of that broader effort.

I like the ability of Rust to state relationships in types and traits instead of leaving them as an informal agreement between functions. A value that belongs to an implementation should be discoverable through that implementation. When it is constant data, the interface should be able to say so.

A type can name a fixed value

Associated functions already provide operations attached to a type without requiring an instance. An associated constant supplies data through the same kind of type-qualified name. The announcement demonstrates a structure with an identifier declared inside its implementation block.

struct Format;

impl Format {
    const ID: u32 = 7;
}

fn main() {
    println!("{}", Format::ID);
}

The value seven is example data, not a protocol assignment. The point is that ID belongs to the type-level namespace. A caller does not need to construct a Format value to obtain it, and the declaration does not pretend that computing it requires an ordinary runtime function call.

This can keep related information together. A type-specific identifier or fixed numeric property no longer needs a separate module-level name solely because constants were unavailable in the implementation block. The type remains the route through which the caller discovers the item.

It is still important to distinguish association from storage in every instance. The constant is not another field repeated in each value of the structure. It is an item named through the type, with the fixed value supplied by its declaration.

A trait can require the value

Traits add a further capability. They can declare a constant and its type without supplying a value. Each implementation then defines the value it provides for that item. The announcement compares this to declaring an associated type whose concrete choice belongs to the implementation.

trait Identified {
    const ID: u32;
}

struct Record;

impl Identified for Record {
    const ID: u32 = 11;
}

fn main() {
    println!("{}", Record::ID);
}

Here the trait specifies the required name and value type, while the implementation supplies eleven. A consumer can rely on that part of the trait contract instead of knowing which independent module happens to contain the corresponding constant.

The example is deliberately limited to the stable behavior described by the release. Associated constants do not mean arbitrary computations have become valid during constant evaluation. The feature supplies a kind of associated item. Which expressions the compiler permits in other constant contexts remains a separate question.

That distinction helps keep the benefit precise. A library can replace an ordinary zero-argument function used solely to expose fixed data. It does not have to promise a general compile-time programming system in order to gain a more accurate interface for that data.

The floating-point example explains the need

Both the RFC and the release announcement use a floating-point trait as motivation. Functions such as nan() and infinity() return values that are conceptually constants. Because ordinary functions cannot serve as constant expressions in that model, the type modules also need their own constant definitions.

An associated constant lets the trait state NAN and INFINITY directly, with values supplied by each floating-point implementation. That unifies the concept at the interface level. The programmer no longer needs a function form merely to make the value accessible through a trait.

The RFC explains this as an expressiveness difference from associated types. Some type relationships could previously be expressed through extra parameters, though inconveniently. Associated constants provide something that an ordinary trait method cannot supply in a constant-expression context.

The long interval between proposal and stabilization is also informative. A design document can contain a coherent family of ideas without requiring every part to ship together. The announcement explicitly notes internal work on constant evaluation and points to further capabilities as future work. The present feature has a concrete boundary despite that broader direction.

I favor that boundary because it lets an API use a specific new contract without depending on unfinished generality. Fixed data attached to an implementation is common enough to justify its own direct representation. A library can adopt that representation while leaving unrelated compile-time mechanisms alone.

Another stability term has a different meaning

The release also adds sort_unstable and its comparison and key variants. Here unstable describes the ordering of elements that compare equal, not the compiler channel or the API availability. The functions are stable APIs in this release.

The announcement illustrates sorting words by their first letter. A stable sort preserves the original relative order of two words beginning with the same letter. An unstable sort may change that relative order. Code that depends on the preserved order needs the stable algorithm, even when both algorithms satisfy the primary sorting key.

This is another example of a contract that should name the actual property a caller relies on. Associated constants state that an item is fixed data attached to a type or implementation. Sorting stability states what happens to equal-key elements. Rust 1.20 expands the interfaces available for expressing both choices without making their different meanings interchangeable.

Cargo also makes a concrete distinction between configuration and credentials. The announcement says the crates.io token moves from ~/.cargo/config into ~/.cargo/credentials, permitting more restrictive permissions on the secret-bearing file. The earlier configuration file could commonly be readable by other users, which is unsuitable for its authentication token.

The new location separates data with different handling requirements. General build configuration may be useful to inspect or share, while a publishing credential should be limited to its owner. This change concerns local credential storage and permissions, independently of the language addition. It is another reason to read the release beyond the new syntax when maintaining a development environment that publishes crates.