What Is New in Rust 1.63
| Category | Highlights |
|---|---|
| New Features | Scoped threads, BorrowedFd/OwnedFd types, const-initializable Mutex/RwLock/Condvar, turbofish support for functions with impl Trait arguments |
| Improvements | Full migration to non-lexical lifetimes across all editions, assorted minor enhancements |
How do scoped threads work in Rust 1.63?
Scoped threads let you spawn threads that can safely borrow data from the surrounding stack frame.
In practice you call std::thread::scope and pass a closure that receives a scope handle. The handle's spawn method creates threads that are guaranteed to finish before the scope returns, so any borrowed references remain valid.
let mut data = vec![1, 2, 3];
std::thread::scope(|s| {
s.spawn(|_| {
println!("first: {:?}", &data);
});
s.spawn(|_| {
// mutable borrow is allowed because no other thread uses it
let mut sum = 0;
for v in &data { sum += v; }
println!("sum = {}", sum);
});
}); // all threads joined here
// data can be used again
data.push(4);
What are BorrowedFd and OwnedFd and why should I use them?
BorrowedFd and OwnedFd are transparent wrapper types that encode whether a raw file descriptor or handle is borrowed or owned.
- They are marked
#[repr(transparent)], so they have the same ABI as the underlying integer or handle. - Using them in FFI signatures makes ownership semantics explicit to the Rust type system, reducing bugs around double-close or leaked descriptors.
- They are available on Unix, Windows, and WASI platforms.
Can I initialize Mutex, RwLock, and Condvar in a const context now?
Yes, the constructors Mutex::new, RwLock::new and Condvar::new are now const, allowing them to be used in static or const definitions without external crates.
use std::sync::{Mutex, RwLock, Condvar};
static GLOBAL_LOCK: Mutex<i32> = Mutex::new(0);
static GLOBAL_RW: RwLock<bool> = RwLock::new(true);
static GLOBAL_CV: Condvar = Condvar::new();
How does the new turbofish support affect functions with impl Trait parameters?
You can now supply explicit generic arguments for the non-impl Trait type parameters of a function, while the impl Trait argument remains opaque.
Example:
fn combine(a: T, b: impl std::fmt::Display) -> String {
format!("{} {}", a, b)
}
// Explicitly specify T, but not the impl Trait type
let result = combine::(42, "items");
What does the completion of non-lexical lifetimes mean for my code?
The non-lexical borrow checker is now the default for all editions, giving more precise diagnostics without changing program semantics.
- Borrow checking is performed based on actual usage rather than lexical scopes.
- This can surface previously hidden lifetime issues earlier in the compile cycle.
- Existing code continues to compile; only error messages may become clearer.
Frequently Asked Questions
Do I need to rewrite all my existing thread code to use scoped threads?
No, the original std::thread::spawn API remains unchanged and can be used alongside scoped threads.
Is there any runtime overhead when using BorrowedFd instead of RawFd?
No, BorrowedFd is #[repr(transparent)] and compiles to the same representation as a raw file descriptor.
How can I declare a static Mutex without pulling in lazy_static?
You can write static MY_LOCK: Mutex<i32> = Mutex::new(0); because Mutex::new is now const.
Can I still use turbofish on a function that only has impl Trait arguments?
No, turbofish can only specify the explicit generic parameters; impl Trait parameters remain opaque.
Will enabling the full non-lexical lifetime checker change the behavior of my existing programs?
No, it only improves borrow-checking diagnostics without altering program semantics.
What is the simplest way to spawn a scoped thread that borrows a local variable?
You can call std::thread::scope(|s| { s.spawn(|_| { println!("{:?}", &my_var); }); });