What Is New in Rust 1.39
| Category | Highlights |
|---|---|
| New Features | Stabilized async/await, shared references to by-move bindings in match guards, attributes on function parameters |
| Improvements | Borrow-checker migration warnings become hard errors in Rust 2018, additional const fn in the standard library, assorted std, Cargo and Clippy updates |
How do async functions and .await work in Rust 1.39?
Async functions and the .await operator are now stable, letting you write asynchronous code directly in the language.
When you declare async fn fetch() -> Result, the function returns an opaque Future that can be driven to completion with .await. The same syntax works for async { ... } and async move { ... } blocks, which behave like async closures.
async fn get_data() -> Result<String, Box<dyn std::error::Error>> {
let response = reqwest::get("https://example.com").await?;
let body = response.text().await?;
Ok(body)
}
In practice, this means you can replace external async runtimes that required nightly features with stable code, reducing the barrier to adopt async I/O.
Can I take shared references to by-move bindings in match guards?
Yes, Rust 1.39 allows shared references to by-move bindings inside match guard expressions.
Previously the compiler rejected code that borrowed a moved value in a guard. The new rule accepts patterns like:
fn main() {
let array: Box<[u8; 4]> = Box::new([1, 2, 3, 4]);
match array {
nums if nums.iter().sum::() == 10 => {
drop(nums);
}
_ => unreachable!(),
}
}
This change smooths the ergonomics of pattern matching, especially when you need to inspect a moved value without taking ownership again.
How can I place attributes on function parameters?
Attributes such as cfg, lint controls, and procedural-macro helpers can now be attached directly to individual parameters.
Instead of writing separate functions for each configuration, you can write a single signature:
fn len(
#[cfg(windows)] slice: &[u16],
#[cfg(not(windows))] slice: &[u8],
) -> usize {
slice.len()
}
This reduces code duplication and makes macro-generated APIs more readable. The supported attributes include conditional compilation (cfg, cfg_attr), lint levels (allow, warn, deny, forbid), and any helper attributes used by procedural macros.
What changed about borrow-checker migration warnings in Rust 2018?
Migration warnings from the old borrow checker are now hard errors in the 2018 edition.
- The non-lexical lifetimes (NLL) checker has replaced the legacy checker for Rust 2018 code.
- Code that previously compiled with a warning because the old checker accepted it but NLL rejected it will now fail to compile.
- This forces teams to address subtle lifetime issues early, paving the way for the old checker's removal in the next release.
Most projects will see no impact, but codebases that relied on the old checker's lenient behavior must be updated.
Frequently Asked Questions
Do I need to rewrite existing async code to use the stable async/await syntax?
No, existing async code continues to work, but you can now adopt the stable syntax without nightly.
Can I still use match guards without taking references?
Yes, the new rule only relaxes restrictions; all previous guard patterns remain valid.
How do I write a function with platform-specific parameters using attributes?
Write fn len(#[cfg(windows)] slice: &[u16], #[cfg(not(windows))] slice: &[u8]) -> usize { slice.len() }.
Will my Cargo builds fail because of the new borrow-checker errors?
Builds that depended on the old borrow checker's false-positive warnings will now error and need code adjustments.
Which standard library functions became const fn in 1.39?
Several functions, such as slice::len and Option::unwrap_or_default, were promoted to const fn.