Series: The Rust Annals
Vol. I Issue 88 nlopes.dev
Announcing Rust 1.87.0 and ten years of Rust!
Rust 1.87 extends precise captures to traits, adds assembly label operands and anonymous pipes, and marks ten years since Rust 1.0.
Rust 1.87 makes combining process output straightforward with standard-library anonymous pipes, lets most architecture intrinsics whose only safety requirement is enabled target features be called from safe code with those features enabled, and extends precise capture syntax to trait definitions. Inline assembly can now jump to Rust blocks. Released exactly ten years after Rust 1.0, it also expands stable and const APIs and removes the i586 Windows MSVC target.
Anonymous pipes simplify combining process output
1.87 adds access to anonymous pipes to the standard library, including integration with std::process::Command's input/output methods. Joining stdout and stderr into one stream is now relatively straightforward; previously it required either extra threads or platform-specific functions.
use std::process::Command;
use std::io::Read;
let (mut recv, send) = std::io::pipe()?;
let mut command = Command::new("path/to/bin")
// Both stdout and stderr will write to the same pipe, combining the two.
.stdout(send.try_clone()?)
.stderr(send)
.spawn()?;
let mut output = Vec::new();
recv.read_to_end(&mut output)?;
// It's important that we read from the pipe before the process exits, to avoid
// filling the OS buffers if the program emits too much output.
assert!(command.wait()?.success());
The standard-library API list below distinguishes two groups: the first 45 entries are newly stable APIs; the final 29 are previously stable APIs that are now also stable in const contexts.
Matching target features make more intrinsic calls safe
Most std::arch intrinsics that are unsafe only due to requiring target features to be enabled are now callable in safe code that has those features enabled. The following toy program sums an array using manual intrinsics; its core loop can now use safe code because the intrinsics' required AVX2 feature matches the function definition.
The example still uses unsafe blocks for the call after runtime feature detection, slice alignment, and transmutation.
#![forbid(unsafe_op_in_unsafe_fn)]
use std::arch::x86_64::*;
fn sum(slice: &[u32]) -> u32 {
#[cfg(target_arch = "x86_64")]
{
if is_x86_feature_detected!("avx2") {
// SAFETY: We have detected the feature is enabled at runtime,
// so it's safe to call this function.
return unsafe { sum_avx2(slice) };
}
}
slice.iter().sum()
}
#[target_feature(enable = "avx2")]
#[cfg(target_arch = "x86_64")]
fn sum_avx2(slice: &[u32]) -> u32 {
// SAFETY: __m256i and u32 have the same validity.
let (prefix, middle, tail) = unsafe { slice.align_to::<__m256i>() };
let mut sum = prefix.iter().sum::<u32>();
sum += tail.iter().sum::<u32>();
// Core loop is now fully safe code in 1.87, because the intrinsics require
// matching target features (avx2) to the function definition.
let mut base = _mm256_setzero_si256();
for e in middle.iter() {
base = _mm256_add_epi32(base, *e);
}
// SAFETY: __m256i and u32 have the same validity.
let base: [u32; 8] = unsafe { std::mem::transmute(base) };
sum += base.iter().sum::<u32>();
sum
}
Inline assembly can jump to Rust blocks
Inline assembly (asm!) can now jump to labeled blocks within Rust code. This enables more flexible low-level programming, such as implementing optimized control flow in OS kernels or interacting with hardware more efficiently.
- The
asm!macro now supports a label operand, which acts as a jump target. - The label must be a block expression with a return type of
()or!. - The block executes when jumped to, and execution continues after the
asm!block. - Using output and label operands in the same
asm!invocation remains unstable.
unsafe {
asm!(
"jmp {}",
label {
println!("Jumped from asm!");
}
);
}
The inline assembly reference provides further details.
Trait return types gain precise capture syntax
This release stabilizes specifying the captured generic types and lifetimes in trait definitions using impl Trait return types. It extends precise capturing with + use<...> from non-trait functions, where it was stabilized in Rust 1.82.
The example desugarings show the distinction: the ordinary return type has an implicit associated type parameterized by 'a, while the return type with use<Self> has an implicit associated type without that lifetime parameter.
trait Foo {
fn method<'a>(&'a self) -> impl Sized;
// ... desugars to something like:
type Implicit1<'a>: Sized;
fn method_desugared<'a>(&'a self) -> Self::Implicit1<'a>;
// ... whereas with precise capturing ...
fn precise<'a>(&'a self) -> impl Sized + use<Self>;
// ... desugars to something like:
type Implicit2: Sized;
fn precise_desugared<'a>(&'a self) -> Self::Implicit2;
}
The i586 Windows MSVC target is removed
The Tier 2 target i586-pc-windows-msvc has been removed. Its difference from the much more popular Tier 1 target i686-pc-windows-msvc is that it does not require SSE2 instruction support. Windows 10, the minimum required OS version of all windows targets except the win7 targets, itself requires SSE2 instructions.
The Major Change Proposal provides more information.