Series: The Rust Annals

Vol. I Issue 64 nlopes.dev

Announcing Rust 1.63.0

Scoped threads, I/O ownership types, and completion of the non-lexical-lifetimes migration make this a notably substantial release.

Rust 1.63 makes locally borrowed data practical to use in threads, brings ownership semantics to raw file descriptors and handles, and completes the migration to non-lexical lifetimes across every edition. It also permits turbofish syntax for named generic parameters alongside argument-position impl Trait, while new const-capable synchronization constructors simplify global statics.

Scoped threads can safely borrow local data

std::thread::spawn bounds its closure with 'static, so threads ordinarily must own values passed into their closures. Even when threads are expected to be joined before a function returns, borrowing local data previously required workarounds such as placing it in an Arc.

The new std::thread::scope API guarantees that its spawned threads have exited before the scope returns. That guarantee lets scoped threads safely borrow from the local stack frame:

let mut a = vec![1, 2, 3];
let mut x = 0;

std::thread::scope(|s| {
    s.spawn(|| {
        println!("hello from the first scoped thread");
        // We can borrow `a` here.
        dbg!(&a);
    });
    s.spawn(|| {
        println!("hello from the second scoped thread");
        // We can even mutably borrow `x` here,
        // because no other threads are using it.
        x += a[0] + a[2];
    });
    println!("hello from the main thread");
});

// After the scope, we can modify and access our variables again:
a.push(4);
assert_eq!(x, a.len());

File descriptors and handles gain explicit ownership semantics

Code calling platform APIs previously worked directly with platform-specific representations of raw file descriptors on Unix-style platforms or handles on Windows, such as c_int or RawFd. In bindings to native APIs, those representations did not let the type system distinguish an operation that takes ownership, such as close, from one that merely borrows, such as dup.

Rust 1.63 provides wrappers including BorrowedFd and OwnedFd. They are #[repr(transparent)], so extern "C" bindings can take the wrappers directly and encode their ownership semantics. The stabilized wrappers cover cfg(unix) platforms, Windows, and WASI. New APIs are recommended to use these types instead of earlier aliases such as RawFd.

The NLL migration finishes, and turbofish works beside impl Trait

The previous lexical borrow checker has been fully removed from rustc across all editions, completing the migration that began with non-lexical lifetimes in the 2018 edition. Because the borrow checker does not affect rustc's output, this does not change program behavior. For most users it brings slightly better diagnostics for some borrow-checking errors, but otherwise does not change which code they can write. The release post links the migration account and the 2018 edition explanation of NLL.

Rust also relaxes a restriction on explicit generic arguments. Given fn foo<T>(value: T, f: impl Copy), earlier compilers rejected foo::<u32>(3, 3):

error[E0632]: cannot provide explicit generic arguments when `impl Trait` is used in argument position
 --> src/lib.rs:4:11
  |
4 |     foo::<u32>(3, 3);
  |           ^^^ explicit generic argument not allowed
  |
  = note: see issue #83701 <https://github.com/rust-lang/rust/issues/83701> for more information

In 1.63, the explicit type of T can be supplied this way. The argument-position impl Trait parameter remains opaque and cannot itself be specified with turbofish, despite desugaring to a generic parameter.

Synchronization constructors and lower-level operations become const-capable

Condvar::new, Mutex::new, and RwLock::new are now callable in const contexts. Global statics containing Mutex, RwLock, or Condvar values can therefore avoid crates such as lazy_static. This builds on Rust 1.62's work enabling thinner and faster mutexes on Linux.

The complete set of APIs newly usable in const contexts is:

  • array::from_ref
  • slice::from_ref
  • intrinsics::copy
  • intrinsics::copy_nonoverlapping
  • <*const T>::copy_to
  • <*const T>::copy_to_nonoverlapping
  • <*mut T>::copy_to
  • <*mut T>::copy_to_nonoverlapping
  • <*mut T>::copy_from
  • <*mut T>::copy_from_nonoverlapping
  • str::from_utf8
  • Utf8Error::error_len
  • Utf8Error::valid_up_to
  • Condvar::new
  • Mutex::new
  • RwLock::new

Reproduced from the Rust blog under its publication licence. Typeset in Literata.