Rust 1.13 gives error propagation a postfix operator
The question mark operator preserves the Result model while removing repeated propagation matches. Its history through try! shows how a library convention can establish the case for language syntax.
Error propagation can occupy more space than the operation whose failure it handles. A function opens a file, matches the result, extracts the handle, reads it, and matches another result. When both error branches only return the error to the caller, the repeated structure contributes little new information. Rust 1.13 gives that operation the postfix ? syntax while retaining explicit result types.
The November 10 release announcement explains the change through a file-reading example. It also makes the history clear. The try! macro has supplied the same propagation behavior since Rust 1.0. The new operator follows an established idiom whose usefulness had already been tested in ordinary code.
I am impressed by how Rust combines low-level control with rules that make ownership and failure visible. Error propagation is one place where those semantics can remain explicit without requiring the same match expression at every call. A reader still sees which operation can return early.
The result still determines control flow
In this release, ? operates on a Result. An Ok supplies its contained value to the surrounding expression. An Err returns an error from the current function. The operator therefore has a control-flow effect even though it appears at the end of an expression.
A small example can show both failure points without repeating their match arms. The imports and return type are part of the example, because the read operation comes from the Read trait and the function has to return a compatible result.
use std::fs::File;
use std::io::{self, Read};
fn read_name() -> Result<String, io::Error> {
let mut file = File::open("name.txt")?;
let mut name = String::new();
file.read_to_string(&mut name)?;
Ok(name)
}
Failure to open the file leaves the function before it attempts a read. Failure during read_to_string also returns an error. The final Ok supplies the accumulated string only after both operations succeed. The abbreviated syntax does not turn failure into a successful empty string or make the caller ignore it.
The example is closely related to the one in the release announcement. Its significance is the placement of the policy. Each ? says the current function declines to recover locally and passes failure to its caller. Code that needs a different policy still needs to express that policy through a match or another appropriate operation.
Conversion belongs to propagation
The Rust 1.13 macro source documents the conversion performed by try!. Its error branch calls From::from and returns the resulting error. The announcement describes ? as equivalent to that established propagation mechanism.
This is why propagation can cross an error-type boundary when the necessary conversion exists. A specialized error does not have to be the exact error type returned by the surrounding function, but there must be a supported conversion. The operator does not invent a message or erase the error into an arbitrary string.
The versioned macro documentation gives an example with a custom error and an implementation of From<io::Error>. That makes the relationship concrete. A function can expose an application-level error type while using an I/O operation internally. The conversion rule states how the lower-level failure becomes part of that public result.
The same source also explains the return-type restriction for the macro. Early propagation needs a function returning Result. For this release, treating ? as a general escape from any function would misdescribe the stable feature. Its concise spelling operates within the existing error model.
This preserves a useful separation between detection and policy. An operation detects and returns its failure. The current function chooses whether to handle it or propagate it, and a conversion can express the relationship between the error types. Reducing the syntax does not remove those decisions from the types.
Postfix syntax makes composition easier to read
The announcement gives nested method calls as a reason to add syntax when a macro already exists. Several successive try! calls place wrappers around increasingly large expressions. The reader has to match the nesting to determine which successful value feeds the next method.
A postfix operator appears next to the operation whose result it inspects. In a chain, the operations remain in their evaluation order and each propagation point follows its call. That is a concrete readability improvement when constructing an object through several fallible steps.
This does not mean every long chain becomes clear. The operator removes one kind of nesting, while the programmer still chooses names and decomposition. The relevant improvement is that the propagation wrapper no longer dominates the expression. A sequence of operations can remain visible without expanding every ordinary error branch.
The announcement also identifies a diagnostic benefit from dedicated syntax. A compiler can tailor an error message to the operator rather than explaining failure inside a general macro expansion. It carefully describes that as an opportunity for improvement, acknowledging that diagnostics for the new syntax still need work in this release.
A macro established the need
The history from try! to ? is informative for language design. The macro system let users try the convention without requiring an immediate language extension. Frequent idiomatic use supplied evidence that the behavior deserved a smaller spelling.
The semantics did not need to be invented at the same time as the syntax. Developers already understood matching an Ok or returning an Err, and the macro captured that pattern. The language addition changes how the common operation appears while retaining the result-based contract that callers use.
I favor that path because a useful abstraction can demonstrate its value before every reader has to learn a new operator. The case for this change includes repeated use and a specific composition problem. It is more concrete than shortening code solely to reduce its line count.
The old macro remains understandable through its source expansion. That is useful when reviewing code that uses both styles during a transition. A reader can compare the error branch directly and see that the early return and conversion are part of the propagation operation, whichever spelling the code uses.
Compiler work reduces a separate cost
Rust 1.13 also includes compilation-performance work. The announcement describes caching normalized projections while generating LLVM intermediate representation. Instead of repeatedly computing concrete instances of associated types, the compiler reuses results it has already calculated.
The reported benefit depends on the code. The announcement cites debug build-time improvement of up to forty percent for futures-rs, not a universal forty-percent speedup. That distinction matters because the optimization targets repeated work associated with particular type patterns. Code that does not exercise those patterns has less of that work to remove.
Another optimization avoids generating code for some inline functions in the defining crate when they are only consumed elsewhere. The announcement explains that the compiler stores the intermediate representation for use by downstream crates. Generating an unused local copy would spend time translating and optimizing code that the defining crate does not call.
The release reports a fifty-percent build-time improvement for ndarray in that context. Again, the mechanism explains why a particular crate benefits. It does not imply that every project now compiles in half the time. These improvements reduce the waiting between editing source and obtaining compiler feedback, while ? reduces repeated structure in the source being edited.
The two changes address different costs of using a strict systems language. Static checks can make failure and ownership constraints useful during development, but developers still need to understand the code and obtain feedback in a reasonable time. Rust 1.13 supplies a smaller spelling for an established error operation and removes identified redundant work in compilation. The error type remains visible in the function signature, so callers can continue matching success and failure according to their own policy.
Destruction bookkeeping moves out of values
Another implementation change removes inline drop flags. The announcement explains that the compiler previously stored a flag inside some structures to record whether a value needed destruction after a conditional move. That could increase the size of a structure, including in code that did not use the conditional move responsible for needing the bookkeeping.
The release instead puts the required flags in extra slots in the stack frames of functions that need them. The announcement connects this to the intermediate representation that became the default in Rust 1.12. That representation gave the compiler a basis for moving this bookkeeping out of the value itself.
This affects the relationship between ownership semantics and representation. The compiler still needs to know whether destruction is required, but that information no longer has to occupy space inside the structure. Its announcement specifically notes that the old arrangement interfered with passing types with destructors through a foreign-function interface. The change removes that particular source of unexpected inline storage without making arbitrary Rust types automatically suitable for foreign interfaces.