The Rust 2024 edition is the most feature-rich edition since 2021, introducing long-awaited language features that make async programming, iteration, and error handling significantly more ergonomic.
🦀 Major Features at a Glance
- Async Closures —
async || {}syntax for async closures - Gen Blocks —
gen { yield value }for easy iterator creation - Never Type (!) — Stabilised for exhaustive matching
- RPITIT — Return-position impl Trait in Traits
- Unsafe Extern Blocks — Better FFI safety
1. Async Closures
Previously, creating async closures required workarounds. Now they're first-class:
// Before: Awkward workaround
let fetch_user = |id: u64| {
let id = id; // move into async block
async move {
reqwest::get(format!("/users/{id}"))
.await?
.json::<User>()
.await
}
};
// After: Clean async closure syntax
let fetch_user = async |id: u64| -> Result<User, Error> {
reqwest::get(format!("/users/{id}"))
.await?
.json::<User>()
.await
};
// Use with async iterators
let users: Vec<User> = ids
.iter()
.map(async |id| fetch_user(*id).await.unwrap())
.collect()
.await;
2. Gen Blocks — Easy Iterators
Creating iterators used to require verbose struct + Iterator trait implementations. Gen blocks make it trivial:
// Before: Manual Iterator implementation (30+ lines)
struct Fibonacci { a: u64, b: u64 }
impl Iterator for Fibonacci {
type Item = u64;
fn next(&mut self) -> Option<u64> {
let result = self.a;
(self.a, self.b) = (self.b, self.a + self.b);
Some(result)
}
}
// After: Gen block (5 lines!)
fn fibonacci() -> impl Iterator<Item = u64> {
gen {
let (mut a, mut b) = (0, 1);
loop {
yield a;
(a, b) = (b, a + b);
}
}
}
// Usage
for n in fibonacci().take(10) {
println!("{n}"); // 0, 1, 1, 2, 3, 5, 8, 13, 21, 34
}
3. Never Type (!) Stabilised
The ! type represents computations that never complete — useful for exhaustive pattern matching and error handling.
// Function that never returns
fn exit_with_error(msg: &str) -> ! {
eprintln!("Fatal: {msg}");
std::process::exit(1);
}
// Exhaustive matching with !
enum Result<T> {
Ok(T),
Err(!), // This variant can never be constructed
}
// The compiler knows Err is impossible, so:
fn always_succeeds() -> Result<String> {
Result::Ok("hello".into())
}
let value = match always_succeeds() {
Result::Ok(v) => v,
// No Err arm needed — compiler knows it's unreachable!
};
4. RPITIT — Traits with impl Trait Returns
Traits can now return impl Trait without needing Box<dyn Trait>:
trait DataSource {
// Return an iterator — each implementor picks concrete type
fn records(&self) -> impl Iterator<Item = Record>;
// Return a future — enables async trait methods
fn fetch(&self, id: u64) -> impl Future<Output = Record>;
}
struct Database;
impl DataSource for Database {
fn records(&self) -> impl Iterator<Item = Record> {
self.cache.iter().cloned()
}
async fn fetch(&self, id: u64) -> Record {
sqlx::query_as("SELECT * FROM records WHERE id = ?")
.bind(id)
.fetch_one(&self.pool)
.await
.unwrap()
}
}
5. Unsafe Extern Blocks
// All items in unsafe extern are implicitly unsafe to call
unsafe extern "C" {
// Compiler ensures you handle safety correctly
safe fn strlen(s: *const c_char) -> usize; // marked safe
fn malloc(size: usize) -> *mut c_void; // remains unsafe
}
// strlen can be called without unsafe block
let len = strlen(c_string.as_ptr());
// malloc still requires unsafe
let ptr = unsafe { malloc(1024) };
🦀 Key Takeaway: The Rust 2024 edition makes the language significantly more ergonomic without sacrificing safety. Gen blocks and async closures alone will transform how you write iterators and async code.