anyhow ensure! 遇到 const 块报 no rules expected keyword const
内联 const 是合法 Rust 表达式,但 anyhow 1.0.104 的内部宏仍按旧 edition 的 expr 片段匹配。给 const 块加括号或先绑定常量,避免为了一个调用改依赖内部宏或错误升级整个项目的 edition。
同一段 const { ... } 能写在普通 Rust 表达式或 assert! 里,交给 anyhow::ensure! 却报宏匹配错误:
anyhow::ensure!(const { 1 + 1 } == 3);
error: no rules expected keyword `const`
在正式 anyhow 1.0.104 中,比较左边、右边以 const 块开头,或整个条件就是一个 const 块,都可能遇到这一错误。它发生在编译期宏展开阶段,不是条件为假以后返回了 anyhow 错误,也不是 const 里的运算本身不合法。
宏定义所用的 edition 仍然有影响
ensure! 会先解析条件,再把比较的两侧或整个条件交给内部宏。1.0.104 的 __fancy_ensure 与 __fallback_ensure 使用 expr 片段,而该 crate 的 Cargo 配置仍是 edition 2021。
在 2021 及更早 edition 中,这类片段不会把开头的内联 const 块当成可匹配的顶层表达式;Rust 2024 才扩展了 expr 对 const 和下划线表达式的支持。宏的规则由定义位置的 edition 决定,仅把调用方的 Cargo.toml 改成 edition 2024,不会重写依赖中已经定义的规则。
因此,编译器能解析 const 块,与某个依赖宏能接受该 token 形状,是两项不同条件。下面的写法面向支持内联 const 的工具链;内联 const 自 Rust 1.79 起可用,不能拿它修复更旧编译器不支持该语法的问题。
让比较操作数从括号开始
可以只包住内联 const 块,让内部表达式匹配器先看到括号组:
use anyhow::{ensure, Result};
fn main() -> Result<()> {
ensure!((const { 1 + 1 }) == 2);
ensure!((const { true }));
Ok(())
}
示例采用 Rust 2021 edition,并依赖 anyhow 1.0.104。两个条件为真时返回 Ok;若把第一个比较值改为 3,应得到普通的 anyhow 条件失败结果,而不是编译期的 no rules expected。
出错位置在右侧时,也只需把该操作数包起来,例如 ensure!(value == (const { 1 + 1 }));。不要为了让解析器绕过去,随手改成多余的逻辑运算;特别是带有函数调用或副作用的条件,增加短路分支可能改变执行行为。
无自定义消息的比较错误中,条件文本可能保留新增的括号。若程序或测试依赖整条错误字符串,应一起核对;括号解决匹配问题,并不承诺错误文案逐字不变。
复用的常量可以先命名
如果值本来就要在多处使用,命名常量往往更清楚:
use anyhow::{ensure, Result};
const MAX_ITEMS: usize = 4;
fn check_count(count: usize) -> Result<()> {
ensure!(count <= MAX_ITEMS, "too many items: {}", count);
Ok(())
}
这里没有改变 ensure! 在条件失败时提前返回 Err 的含义。也不要改用 assert! 只求编译通过:assert 的失败行为是 panic,与函数返回可处理错误不同。
截至 2026 年 10 月 25 日,当前正式 1.0.104 的相关宏仍有上述匹配边界。无需为一个调用修改依赖缓存中的内部宏;那样的补丁容易在重新下载或 CI 构建时丢失。先保留原依赖版本与工具链配置,分别检查真条件、假条件、自定义消息和真实调用位置的编译结果。若后续采用新正式版本,再核对它实际接受的语法及错误输出,不把尚未进入发行版的修改当成已可用修复。


