June 21, 2018·5 min read·engineeringrustsimdtraits

Rust 1.27 exposes CPU intrinsics and names dynamic dispatch

Stable architecture intrinsics let libraries choose specialized CPU implementations. The dyn Trait spelling makes an existing runtime dispatch choice visible in source.

A binary distributed to several machines cannot assume they all support the instruction set of the developer's CPU. That constraint limits which specialized instructions a compiler may use freely. Rust 1.27 gives stable code access to architecture intrinsics and mechanisms for selecting an implementation suited to the machine. Its release announcement explains both compile-time selection and runtime detection.

The same release stabilizes dyn Trait as an explicit spelling for trait objects. These additions operate at different levels, but both make a choice easier to see. A library can identify the CPU capability needed by a specialized function. An interface can identify where it uses a trait object for dynamic dispatch.

I like Rust most where it makes the costs and requirements of an abstraction available for inspection. The intrinsics addition provides control that systems code sometimes needs. The trait-object spelling makes an existing representation decision easier for a reader to recognize.

Automatic vectorization already exists

SIMD instructions operate on multiple data elements through one instruction. The release article uses element-wise addition of byte slices as an example. A compiler can sometimes recognize that work and vectorize it automatically. Stable Rust could benefit from that optimization before this release.

The new capability is explicit access through std::arch. That matters when automatic vectorization does not identify a suitable transformation or when a program needs an architecture operation directly. It also lets libraries build higher-level APIs over those operations while continuing to support the stable compiler.

RFC 2325 describes the intended framework. The initial stable intrinsics are architecture-specific, with support machinery for other targets and later higher-level designs. This is not the stabilization of a single portable vector type that works identically on every CPU architecture.

That boundary changes how the addition should be described. A library can use the new facilities to build a portable public function with specialized implementations underneath. The individual intrinsic still belongs to an architecture and carries its requirements. Portability comes from the library's selection and fallback design, not from pretending every machine implements the same instruction.

Compile-time selection describes a target promise

The announcement shows conditional compilation using architecture and target-feature conditions. This can select code when the chosen target includes a particular CPU capability. The decision is about the environment the compiler is instructed to target. It does not independently inspect every machine that may later execute the binary.

For a controlled deployment, a known target capability can be a useful contract. For a generally distributed program, that assumption can be too strong. The application author needs to distinguish the machine used to build the program from the set of machines expected to run it.

A source-level conditional helps keep architecture-specific imports out of unrelated builds. Code for x86 and x86-64 uses the relevant module for that architecture. That is a compile-time organization problem separate from deciding whether the executing CPU supports a particular optional feature.

Runtime detection selects within one binary

The release also introduces is_x86_feature_detected! for runtime checks on the relevant architectures. Its example checks for AVX2 and calls a specialized implementation only when the capability exists. Otherwise, a fallback handles the operation.

The specialization still has a safety requirement. Reaching a function that assumes unsupported instructions is not made valid merely because the function compiled. The dispatch code has to establish the capability before making the call. The announcement's unsafe call is connected to that obligation.

This provides a concrete pattern for library design. A public operation can accept ordinary slices and keep CPU selection inside the implementation. Callers need not choose an architecture function themselves. The library owns the responsibility for selecting a valid implementation while preserving the operation's documented behavior.

The fallback is part of that behavior. It needs to compute the same intended result as the specialized path, including the conditions the API promises around input lengths and arithmetic. An instruction-level optimization does not authorize a different public meaning on another CPU. This follows from exposing one public operation with multiple implementations.

Trait objects get a visible marker

The dyn syntax design in RFC 2113 addresses confusion between naming a trait and constructing a trait-object type. Before the new spelling, Box<Foo> could mean a boxed concrete struct or a boxed trait object depending on what Foo denoted. The same compact form concealed an important distinction.

Rust 1.27 permits Box<dyn Foo> for a boxed trait object and &dyn Foo for a borrowed one. The marker identifies the dynamic interface. It does not introduce a new dispatch mechanism or change the old object's functionality.

The borrowed spelling also prevents an overgeneralization about allocation. A trait object need not be owned through a box. A reference can borrow an existing value and dispatch through the trait-object interface. The storage decision and the dispatch decision are related in many APIs, but they are not the same thing.

The old bare-trait spelling remains accepted for compatibility. The release includes a lint that authors can enable, but it allows the old syntax by default. Existing source is not required to change simply because a clearer spelling is available.

I prefer an explicit marker here because it helps a reader distinguish this choice from impl Trait, stabilized in the preceding release. The decision concerns whether the interface hides one concrete result or supports dynamic dispatch. Giving both forms recognizable syntax helps teaching without inventing a new runtime model.

The release also discloses an unresolved soundness issue

The announcement reports a late-discovered problem in default match bindings, introduced in Rust 1.26, that may cause unsoundness. It says a patch release is expected. That is the disclosed status today, and the new stable capabilities do not imply that the identified issue has already been fixed in 1.27.0.

For the features discussed here, the immediate contracts are specific. std::arch exposes low-level instructions whose availability the implementation must establish. dyn Trait identifies an existing dynamic interface while retaining compatibility with earlier source. A library can use the first to specialize its implementation and the second to express a runtime abstraction, with each decision visible at the place where it matters.