Rust E0030 解析:为什么 `1000 ..= 5` 这样的范围模式会被 rustc 拒绝
本文讲解 Rust 编译错误 E0030(lower bound for range pattern must be less than or equal to upper bound)的触发条件与底层判定机制:当你在 match 中使用闭区间范围模式(a ..= b)且下界大于上界时,编译器会验证该区间非空并报错。读完后你将理解 rustc 在模式降级(pattern lowering)阶段如何用常量求值与比较逻辑判定区间是否为空、x ..= x 为何被允许并折叠为常量模式,以及与开区间对应的 E0579 之间的区别。
E0030 的规则:范围模式必须是非空区间
rustc 官方错误代码文档对 E0030 的定义见 E0030.md。其核心规则非常简短但明确:
When matching against a range, the compiler verifies that the range is non-empty. Range patterns include both end-points, so this is equivalent to requiring the start of the range to be less than or equal to the end of the range.
即:对范围模式做匹配时,编译器会验证该区间非空。由于 Rust 的 ..= 范围模式同时包含两个端点,非空条件等价于要求“下界小于或等于上界”(start <= end)。注意这里的比较符号是“小于或等于”——x ..= x 是合法的,它恰好只匹配一个值。
官方文档给出的错误代码示例:
match 5u32 {
// This range is ok, albeit pointless.
1 ..= 1 => {}
// This range is empty, and the compiler can tell.
1000 ..= 5 => {}
}
1 ..= 1 虽然“毫无用处”(pointless),但它是合法的非空区间;而 1000 ..= 5 是一个空区间,编译器能确定地识别出来,于是报错。
编译期实际报长什么样
仓库中的 UI 测试用例 E0030.rs 复现了最小触发场景:
fn main() {
match 5u32 {
1000 ..= 5 => {}
}
}
对应的 E0030.stderr 展示了精确的诊断输出:
error[E0030]: lower bound for range pattern must be less than or equal to upper bound
--> $DIR/E0030.rs:3:9
|
LL | 1000 ..= 5 => {}
| ^^^^^^^^^^ lower bound larger than upper bound
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0030`.
要点:
- 错误级别是 hard error(
error[E0030]),不是警告,编译会中止; - 主诊断消息为 “lower bound for range pattern must be less than or equal to upper bound”,附加 label 指出 “lower bound larger than upper bound”,精确标记整个范围表达式
1000 ..= 5; - 提示可用
rustc --explain E0030查看说明——该说明文本即来自错误代码文档的 teach 机制(见下文teach字段)。
仓库中还有一个 E0030-teach.rs 测试,用于验证 --explain 输出时诊断中的教学性 note 能够正确呈现。
源码级原理:E0030 在哪里、如何被触发
诊断定义
E0030 的诊断结构体定义在 diagnostics.rs:
#[derive(Diagnostic)]
#[diag("lower bound for range pattern must be less than or equal to upper bound", code = E0030)]
pub(crate) struct LowerRangeBoundMustBeLessThanOrEqualToUpper {
#[primary_span]
#[label("lower bound larger than upper bound")]
pub(crate) span: Span,
#[note(
"when matching against a range, the compiler verifies that the range is non-empty. Range patterns include both end-points, so this is equivalent to requiring the start of the range to be less than or equal to the end of the range"
)]
pub(crate) teach: bool,
}
几个值得注意的细节:
- 错误代码在
#[diag(...)]宏属性中通过code = E0030绑定; - note 文案与 E0030.md 的正文一致;
teach: bool字段控制是否输出这段教学性说明——当用户通过rustc --explain E0030触发 teach 模式时为true。
触发点:模式降级阶段
报错的调用链位于模式从 HIR 降级为 THIR 的过程,具体在 thir/pattern/mod.rs 的范围模式处理分支中:
let lo = lower_endpoint(lo_expr)?.unwrap_or(PatRangeBoundary::NegInfinity);
let hi = lower_endpoint(hi_expr)?.unwrap_or(PatRangeBoundary::PosInfinity);
let cmp = lo.compare_with(hi, ty, self.tcx);
let mut kind = PatKind::Range(Arc::new(PatRange { lo, hi, end, ty }));
match (end, cmp) {
// `x..y` where `x < y`.
(RangeEnd::Excluded, Some(Ordering::Less)) => {}
// `x..=y` where `x < y`.
(RangeEnd::Included, Some(Ordering::Less)) => {}
// `x..=y` where `x == y` and `x` and `y` are finite.
(RangeEnd::Included, Some(Ordering::Equal)) if lo.is_finite() && hi.is_finite() => {
let value = ty::Value { ty, valtree: lo.as_finite().unwrap() };
kind = PatKind::Constant { value };
}
// `x..y` where `x >= y`, or `x..=y` where `x > y`. The range is empty => error.
_ => {
// Emit a more appropriate message if there was overflow.
self.error_on_literal_overflow(lo_expr, ty)?;
self.error_on_literal_overflow(hi_expr, ty)?;
let e = match end {
RangeEnd::Included => {
self.tcx.dcx().emit_err(LowerRangeBoundMustBeLessThanOrEqualToUpper {
span,
teach: self.tcx.sess.teach(E0030),
})
}
RangeEnd::Excluded if lo_expr.is_none() => {
self.tcx.dcx().emit_err(UpperRangeBoundCannotBeMin { span })
}
RangeEnd::Excluded => {
self.tcx.dcx().emit_err(LowerRangeBoundMustBeLessThanUpper { span })
}
};
return Err(e);
}
}
从这段源码结构看,可以得出几个比文档更细的结论:
- 比较基于常量求值结果。两端点先经过
lower_endpoint求值为PatRangeBoundary(有限值、负无穷或正无穷),再由compare_with得到Ordering。只有当区间确定为空(闭区间x > y,或开区间x >= y)时才报错——cmp为None(如浮点 NaN 无法比较)时不会在此处误报。 x ..= x被折叠为常量模式。源码中(RangeEnd::Included, Ordering::Equal)分支会把两端点相等的闭区间直接改写成PatKind::Constant,这就是1 ..= 1合法的原因——它等价于字面量模式1。- 错误分发按区间端点类型三分:
RangeEnd::Included(..=)且lo > hi→ 发出LowerRangeBoundMustBeLessThanOrEqualToUpper,即 E0030;RangeEnd::Excluded(..)且lo < hi不成立 → 发出LowerRangeBoundMustBeLessThanUpper,即 E0579(开区间要求下界严格小于上界,5 .. 5同样是空区间);RangeEnd::Excluded且下界缺失(形如..5但 5 已是类型最小值的特例)→ 发出UpperRangeBoundCannotBeMin。
- 溢出会优先提示更贴切的信息。空区间错误分支先调用两次
error_on_literal_overflow,若端点字面量本身溢出类型范围,会先给出字面量溢出诊断,避免误导用户去改区间顺序。 teach: self.tcx.sess.teach(E0030)表明教学 note 的显隐由会话的 explain/teach 状态控制,普通编译时只见主诊断与 label。
区间边界比较的实现
compare_with 与边界表示定义在 thir.rs。PatRangeBoundary 是一个三值枚举:
/// A (possibly open) boundary of a range pattern.
/// If present, the const must be of a numeric type.
pub enum PatRangeBoundary<'tcx> {
Finite(ty::ValTree<'tcx>),
NegInfinity,
PosInfinity,
}
compare_with 的实现要点(同文件 第 1045 行起):
- 两个无穷边界同值时直接返回
Equal(0u8..与0u8..=255描述同一区间的规范化前提); - 对整型与
char两端点,源码专门做了“热路径”优化:直接取标量叶子做无符号/有符号数值比较,注释里提到unicode-normalization这类库有大量形如'\u{037A}'..='\u{037F}'的字符区间,因此该路径被特殊加速; - 浮点类型则通过
rustc_apfloat恢复浮点语义后partial_cmp,无法比较(NaN)时返回None,从而不会误触发 E0030。
同文件中还可见 PatRange::contains 与 overlaps(第 952–988 行)复用了同一套比较逻辑来做成员判定和区间重叠判定——也就是说,E0030 的检查只是这套“区间边界比较”基础设施在模式降级入口的一次应用。
E0030 与相邻错误的边界
理解 E0030 时容易与两个相邻诊断混淆,结合 diagnostics.rs 与模式降级分支的代码可以厘清:
| 模式 | 空区间条件 | 触发错误 |
|---|---|---|
x ..= y(闭区间) |
x > y |
E0030:下界必须 ≤ 上界 |
x .. y(开区间) |
x >= y |
E0579:下界必须 < 上界(LowerRangeBoundMustBeLessThanUpper) |
.. y 且 y 为类型最小值(如 u32 的 ..0) |
区间必然为空 | UpperRangeBoundCannotBeMin |
而 x ..= x(两端点相等且有限)不报错,反而被降级为常量模式。这也解释了为什么 E0030 的比较符号是“less than or equal to”——闭区间的端点相等时区间恰好包含一个元素,仍是非空的。
实用建议
- 写范围模式前先确认类型方向:
1 ..= 5、5u8..=255这类区间在编译期即可被验证; - 若需要“只匹配单个值”,直接用字面量模式(如
1)比1 ..= 1更清晰,虽然两者等价; - 若报错提示下界大于上界,先检查常量求值结果(例如由
const表达式计算的端点),因为比较发生在常量求值之后; - 若端点本身超出类型范围,编译器会优先给出字面量溢出诊断,此时应修的是字面量而非区间顺序。
小结
E0030 是 rustc 在模式降级阶段对闭区间范围模式做的非空性静态验证:编译器通过常量求值确定两端点,用 PatRangeBoundary::compare_with 比较,一旦发现 x ..= y 满足 x > y 便报出该错误;x ..= x 则合法并被优化为常量模式。该机制的完整证据链可以在 E0030 文档、诊断定义、模式降级逻辑、边界比较实现 以及 UI 测试 中逐一查证。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00