Rust 2024 Edition新特性

Rust 2024 Edition 随 Rust 1.85 正式发布,这是继 2021 Edition 之后的一次重要迭代。本文梳理其中最值得关注的语言特性变化。

一、async closures

之前在 Rust 中写异步闭包非常别扭,通常需要这样的 workaround:

// 旧写法:返回一个 async block
let fetch = |url: String| async move {
    reqwest::get(&url).await?.text().await
};
// 问题:闭包本身不是 async 的,它返回了一个 Future
// 无法直接用于接受 AsyncFn trait 的地方

2024 Edition 引入了 async 闭包语法和 AsyncFn / AsyncFnMut / AsyncFnOnce trait:

// 新写法
let fetch = async |url: String| {
    reqwest::get(&url).await?.text().await
};

// 可以直接用在需要 AsyncFn 的泛型参数中
async fn retry<F>(f: F, times: u32) -> Result<String, Error>
where
    F: AsyncFn(String) -> Result<String, Error>,
{
    for _ in 0..times {
        if let Ok(result) = f("https://example.com".into()).await {
            return Ok(result);
        }
    }
    Err(Error::MaxRetries)
}

关键区别在于:async || {} 创建的闭包本身实现了 AsyncFn trait,而旧的 || async {} 只是一个返回 Future 的普通闭包。前者可以借用环境变量并在多次调用间保持借用关系,后者做不到。

二、gen blocks — 生成器语法

gen 块让你可以用类似 Python yield 的方式创建迭代器:

fn fibonacci() -> impl Iterator<Item = u64> {
    gen {
        let (mut a, mut b) = (0u64, 1);
        loop {
            yield a;
            (a, b) = (b, a.saturating_add(b));
        }
    }
}

fn main() {
    for n in fibonacci().take(20) {
        print!("{n} ");
    }
    // 0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181
}

gen 块自动实现 Iterator trait,yield 对应 next() 返回 Some(value),块结束对应 None

更实际的用例——遍历树结构:

struct TreeNode {
    val: i32,
    left: Option<Box<TreeNode>>,
    right: Option<Box<TreeNode>>,
}

fn inorder(node: &TreeNode) -> impl Iterator<Item = i32> + '_ {
    gen {
        if let Some(left) = &node.left {
            for val in inorder(left) {
                yield val;
            }
        }
        yield node.val;
        if let Some(right) = &node.right {
            for val in inorder(right) {
                yield val;
            }
        }
    }
}

目前 gen 块还不支持 async gen(异步生成器),这个预计会在后续版本中加入。

三、let chains in if/while

以前 if let 只能匹配单个模式,现在可以用 && 串联多个 let 绑定和布尔条件:

fn process(config: &Option<Config>, request: &Request) {
    // 旧写法需要嵌套
    if let Some(cfg) = config {
        if let Some(auth) = &cfg.auth {
            if auth.is_valid() {
                handle(cfg, auth, request);
            }
        }
    }

    // 新写法:let chains
    if let Some(cfg) = config
        && let Some(auth) = &cfg.auth
        && auth.is_valid()
    {
        handle(cfg, auth, request);
    }
}

while let 同理:

while let Some(item) = queue.pop_front()
    && item.priority > threshold
{
    process(item);
}

这个特性消除了大量嵌套的 if let,在处理 Option/Result 链式解包时代码可读性提升很大。

四、Never type ! 的稳定化

! 类型(Never type)在 2024 Edition 中正式稳定。! 表示一个永远不会产生值的类型,用于标注永远不会返回的函数或分支。

// 标注发散函数
fn exit_with_error(msg: &str) -> ! {
    eprintln!("Fatal: {msg}");
    std::process::exit(1);
}

// 在 match 中使用
fn parse_or_die(s: &str) -> u64 {
    match s.parse::<u64>() {
        Ok(n) => n,
        Err(e) => exit_with_error(&format!("parse failed: {e}")),
        // Err 分支返回 !,可以自动协变为 u64
    }
}

稳定化之后,! 可以出现在更多位置。比如 Result<T, !> 表示永远不会失败的 Result,编译器能据此做更好的优化:

// 一个永远成功的转换
fn infallible_convert(x: u32) -> Result<u64, !> {
    Ok(x as u64)
}

fn main() {
    // 编译器知道 Err 分支不可能到达
    let Ok(val) = infallible_convert(42);
    println!("{val}");
}

五、impl Trait 位置扩展

2024 Edition 扩展了 impl Trait 可以出现的位置。以前 impl Trait 主要用在函数参数和返回值中,现在可以出现在 trait 定义中:

trait HttpClient {
    // 关联类型使用 impl Trait(Return Position Impl Trait in Trait)
    fn get(&self, url: &str) -> impl Future<Output = Result<Response, Error>> + Send;
}

struct ReqwestClient;

impl HttpClient for ReqwestClient {
    fn get(&self, url: &str) -> impl Future<Output = Result<Response, Error>> + Send {
        async {
            let resp = reqwest::get(url).await?;
            Ok(resp)
        }
    }
}

这极大简化了异步 trait 的定义——之前要么用 #[async_trait] 宏(有 Box 开销),要么手动定义关联类型,现在直接写 -> impl Future 就行了。

六、其他变化

RPIT(Return Position Impl Trait)的生命周期捕获规则变更:2024 Edition 中 -> impl Trait 默认捕获所有输入生命周期参数。如果需要旧行为,使用 + use<'a> 语法显式指定捕获哪些生命周期。

unsafe_op_in_unsafe_fn 默认开启:在 unsafe fn 中的 unsafe 操作现在必须包在 unsafe {} 块中。这让 unsafe 的边界更加精确。

// 2024 Edition 要求
unsafe fn dangerous_copy(src: *const u8, dst: *mut u8, len: usize) {
    // 必须显式标注 unsafe 块
    unsafe {
        std::ptr::copy_nonoverlapping(src, dst, len);
    }
}

gen 成为保留关键字:如果你的代码中有变量叫 gen,需要改用 r#gen 或换个名字。

总结

2024 Edition 的变更中,async closures 和 gen blocks 对日常开发影响最大。前者终于让异步编程中的闭包使用达到了合理的人机工效,后者给 Rust 带来了期待已久的生成器语法。let chains 则是一个小而美的改进,消除了令人头疼的嵌套解包。迁移方面,cargo fix --edition 2024 可以自动处理大部分兼容性问题。