January 17, 2019·5 min read·engineeringrustallocationdebuggingreleases

Rust 1.32 makes the allocator choice explicit

Rust 1.32 defaults to the system allocator after making allocator selection available to applications. The new dbg! macro also makes temporary expression inspection easier without changing the expression's result.

Removing a default is easier after giving users a supported way to choose it explicitly. Rust 1.32 finishes that sequence for jemalloc. Programs now use the system allocator by default, while applications that need jemalloc can select it through a crate and a global allocator declaration.

The January 17 release announcement explains the history. Rust once had a substantial runtime, and jemalloc remained after most of that runtime disappeared. The project could not simply remove it without affecting users who depended on its performance, because those users initially had no supported way to make their own global allocator choice.

I find this direction consistent with Rust's appeal as a systems language. Allocation policy has consequences for a program's integration with its environment. Making it selectable lets the application state its requirement rather than inheriting a language-wide performance assumption.

The earlier release supplied the selection mechanism

Rust 1.28 stabilized the #[global_allocator] attribute and the GlobalAlloc trait. Its August 2018 announcement showed how to select std::alloc::System explicitly. It also described implementing a new allocator for an application domain.

At that time, some platforms still defaulted to jemalloc. The selection mechanism separated two decisions, what Rust supplied by default and what an application could require. Rust 1.32 changes the first decision using the mechanism established by the earlier release.

The platform qualification matters. Jemalloc was not the previous default everywhere. The January announcement specifically notes that Windows MSVC targets already used the system allocator. Describing the change as removing jemalloc from every Rust program would obscure the difference between targets.

The announcement says jemalloc adds about 300 kilobytes to a Rust binary and discusses performance and integration problems as reasons for the change. That size is the release team's approximate figure, not a new measurement of every executable. The article also recognizes that jemalloc often performs well. Its argument is for application choice instead of one allocator being universally fastest.

Keeping jemalloc is an application declaration

The release post gives jemallocator version 0.1.8 as the dependency for retaining jemalloc. The corresponding entry belongs in the manifest's dependency section.

[dependencies]
jemallocator = "0.1.8"

The crate root then names the global allocator using the stable attribute. The static declaration identifies the allocator implementation selected for the program.

#[global_allocator]
static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc;

This is an explicit build input and code declaration. It records an application's allocator requirement where another engineer can find it. Programs that accept the new system default need no equivalent jemalloc dependency merely to preserve Rust's earlier platform policy.

The release does not provide a workload-independent performance result for switching allocators. Its concrete contribution is the completed move to the system default with a documented route to retain jemalloc. An application with a specific allocator requirement can represent that requirement directly.

dbg! preserves an expression's result

The other prominent change in Rust 1.32 is the dbg! macro. It prints the source file and line, the expression and its debug representation to standard error. It also returns the expression's value, allowing it to be inserted into an existing expression without replacing the surrounding computation with a separate print statement.

For a small example using a copyable integer, the returned value remains available to the assignment. The assertion checks the value produced by that expression.

fn main() {
    let count = 5;
    let doubled = dbg!(count * 2);
    assert_eq!(doubled, 10);
}

The result-returning behavior is the important distinction from eprintln!, which returns (). A print macro supplies output, while dbg! can supply both temporary diagnostic output and the original expression result. The release article illustrates this with recursive calculations and conditional expressions.

Including the expression and location also solves a practical ambiguity in temporary diagnostics. Several ordinary debug prints can show the same values without identifying which call produced each line. A programmer can add labels manually, but the macro supplies this context consistently at the invocation site.

Standard error is a deliberate output choice. The release notes point out that ordinary println! writes standard output, which may already belong to the program's useful output. The macro avoids mixing temporary inspection with that stream, though it still introduces diagnostic output that the programmer must account for.

The versioned macro documentation adds an ownership qualification. The macro moves its expression before returning the evaluated value. For a type without Copy, discarding that returned value can therefore consume the original variable. Passing a reference, such as dbg!(&value), permits inspection without transferring ownership of the value itself.

That documentation also says the precise printed format is not a stable contract and identifies temporary debugging as the intended use. A parser should not depend on a particular arrangement of the macro's output. Its benefit is contextual inspection during development, while persistent structured diagnostics require their own deliberate output contract.

I appreciate small tools that preserve the code's existing structure while exposing enough information to understand it. Here the macro returns the expression result and adds its location to the diagnostic. That reduces the editing needed to inspect a calculation without claiming to replace a debugger or a program's logging policy.

Uniform paths reduce a separate inconsistency

Rust 1.32 also completes a module-path adjustment for the 2018 edition. The announcement describes uniform paths, allowing certain import paths to resolve the same way as paths used outside imports. Its example imports variants from a locally defined Color enum with use Color::*.

Previously, the example's import needed a different starting form, such as self, super or crate, under the rules described in the release post. The new resolution makes the local enum path usable in the import itself. This is an edition-specific language change, separate from both allocator selection and the library macro.

The common practical benefit of these changes is more specific than a general claim that Rust is simpler. Allocation policy can be declared by the application. Temporary diagnostics can include expression context without replacing the expression's result. Imports in the 2018 edition gain a more consistent resolution rule, reducing one distinction a reader has to remember when following names through a module.