首页
/ Rust E0030 解析:为什么 `1000 ..= 5` 这样的范围模式会被 rustc 拒绝

Rust E0030 解析:为什么 `1000 ..= 5` 这样的范围模式会被 rustc 拒绝

2026-09-06 15:57:50作者:何将鹤

本文讲解 Rust 编译错误 E0030lower 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);
    }
}

从这段源码结构看,可以得出几个比文档更细的结论:

  1. 比较基于常量求值结果。两端点先经过 lower_endpoint 求值为 PatRangeBoundary(有限值、负无穷或正无穷),再由 compare_with 得到 Ordering。只有当区间确定为空(闭区间 x > y,或开区间 x >= y)时才报错——cmpNone(如浮点 NaN 无法比较)时不会在此处误报。
  2. x ..= x 被折叠为常量模式。源码中 (RangeEnd::Included, Ordering::Equal) 分支会把两端点相等的闭区间直接改写成 PatKind::Constant,这就是 1 ..= 1 合法的原因——它等价于字面量模式 1
  3. 错误分发按区间端点类型三分
    • RangeEnd::Included..=)且 lo > hi → 发出 LowerRangeBoundMustBeLessThanOrEqualToUpper,即 E0030
    • RangeEnd::Excluded..)且 lo < hi 不成立 → 发出 LowerRangeBoundMustBeLessThanUpper,即 E0579(开区间要求下界严格小于上界,5 .. 5 同样是空区间);
    • RangeEnd::Excluded 且下界缺失(形如 ..5 但 5 已是类型最小值的特例)→ 发出 UpperRangeBoundCannotBeMin
  4. 溢出会优先提示更贴切的信息。空区间错误分支先调用两次 error_on_literal_overflow,若端点字面量本身溢出类型范围,会先给出字面量溢出诊断,避免误导用户去改区间顺序。
  5. teach: self.tcx.sess.teach(E0030) 表明教学 note 的显隐由会话的 explain/teach 状态控制,普通编译时只见主诊断与 label。

区间边界比较的实现

compare_with 与边界表示定义在 thir.rsPatRangeBoundary 是一个三值枚举:

/// 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 行起):

  • 两个无穷边界同值时直接返回 Equal0u8..0u8..=255 描述同一区间的规范化前提);
  • 对整型与 char 两端点,源码专门做了“热路径”优化:直接取标量叶子做无符号/有符号数值比较,注释里提到 unicode-normalization 这类库有大量形如 '\u{037A}'..='\u{037F}' 的字符区间,因此该路径被特殊加速;
  • 浮点类型则通过 rustc_apfloat 恢复浮点语义后 partial_cmp,无法比较(NaN)时返回 None,从而不会误触发 E0030。

同文件中还可见 PatRange::containsoverlaps第 952–988 行)复用了同一套比较逻辑来做成员判定和区间重叠判定——也就是说,E0030 的检查只是这套“区间边界比较”基础设施在模式降级入口的一次应用。

E0030 与相邻错误的边界

理解 E0030 时容易与两个相邻诊断混淆,结合 diagnostics.rs 与模式降级分支的代码可以厘清:

模式 空区间条件 触发错误
x ..= y(闭区间) x > y E0030:下界必须 ≤ 上界
x .. y(开区间) x >= y E0579:下界必须 < 上界(LowerRangeBoundMustBeLessThanUpper
.. yy 为类型最小值(如 u32..0 区间必然为空 UpperRangeBoundCannotBeMin

x ..= x(两端点相等且有限)不报错,反而被降级为常量模式。这也解释了为什么 E0030 的比较符号是“less than or equal to”——闭区间的端点相等时区间恰好包含一个元素,仍是非空的。

实用建议

  • 写范围模式前先确认类型方向:1 ..= 55u8..=255 这类区间在编译期即可被验证;
  • 若需要“只匹配单个值”,直接用字面量模式(如 1)比 1 ..= 1 更清晰,虽然两者等价;
  • 若报错提示下界大于上界,先检查常量求值结果(例如由 const 表达式计算的端点),因为比较发生在常量求值之后;
  • 若端点本身超出类型范围,编译器会优先给出字面量溢出诊断,此时应修的是字面量而非区间顺序。

小结

E0030 是 rustc 在模式降级阶段对闭区间范围模式做的非空性静态验证:编译器通过常量求值确定两端点,用 PatRangeBoundary::compare_with 比较,一旦发现 x ..= y 满足 x > y 便报出该错误;x ..= x 则合法并被优化为常量模式。该机制的完整证据链可以在 E0030 文档诊断定义模式降级逻辑边界比较实现 以及 UI 测试 中逐一查证。

登录后查看全文
热门项目推荐
相关项目推荐