Rust 编译错误 E0203 深入解析:重复的 relaxed default bounds(?Sized重复声明)
【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rust
导读
E0203 是 rustc 在类型参数约束解析与 HIR 降级(lowering)阶段报出的编译错误,含义为"重复的放宽默认约束(duplicate relaxed default bounds)不被支持"。本文以 rustc_error_codes 错误字典中 E0203 官方条目 为骨架,结合 rustc 编译器源码中真正签发该错误的 rustc_ast_lowering 实现与 compiletest 用例,讲清它是什么、在哪些场景触发、rustc 底层如何检测重复,以及如何修复,帮助你在阅读编译错误时快速定位根因。
错误本体:一条简短的硬错误
E0203 对应的错误信息为:
Having duplicate relaxed default bounds is unsupported.
(存在重复的放宽默认约束是不受支持的。)
它是一条编译期硬错误(hard error),一旦触发,代码无法通过编译。其语法结构可概括为:同一个泛型参数(或关联类型)的 bound 列表中出现了两个相同的、带放宽修饰符?的约束,例如连续写两个?Sized。
触发场景:原文档的官方示例
rustc 错误字典中 E0203.md 给出了最小复现:
struct Bad<T: ?Sized + ?Sized>{ inner: T, }这里,类型参数T的约束列表中,?Sized出现了两次。Sized是泛型参数默认携带的 trait 约束,而?Sized表示"放宽"(relax)该默认约束,即允许T是不定长类型(unsized type)。当同一个默认约束被重复放宽时,语义上存在冗余与歧义,rustc 判定其不受支持并签发 E0203。
值得注意的是,该错误并不局限于struct的定义处。从源码的报错路径看,凡是在泛型约束中被判定为"重复放宽同一 trait"的位置都会触发 E0203,例如fn的泛型参数上同样适用(见下文测试用例分析)。
语言机制背景:什么是 "relaxed default bound"
要理解 E0203,需要先建立三个概念:
- 默认约束(default bound):在 Rust 中,
Sized是所有泛型类型参数的隐式约束——除非显式写出?Sized,否则编译器默认T: Sized。这种无需用户书写、由语言自动施加的 trait 约束称为 default bound。除了Sized,从编译器注释看,未来还可能引入其他默认 trait(如假设性的Move、Leak),见 lib.rs 中的 FIXME 注释。 - 放宽约束(relaxed bound):
?Trait语法表示把对应的默认约束从泛型参数上移除。稳定通道上只有?Sized可用(通过maybe_bound特性实现);在more_maybe_bounds这一内部 gate 下,其他默认 trait 的放宽才被允许(见 validate_relaxed_bound 的注释)。 - 放宽默认约束的目的:它让类型参数可以接受如
[T]、str、dyn Trait等不定长类型,是编写零拷贝切片处理、胖指针抽象、trait object 集合等代码的基础设施。
E0203 只关心"重复",与"单次放宽"是否合法无关——单次?Sized是完全合法且常用的写法。
源码级原理:rustc 在哪一步、如何判定重复
E0203 并非由类型检查阶段产生,而是在抽象语法树(AST)向高层中间表示(HIR)的降级过程中签发。签发点在 compiler/rustc_ast_lowering/src/lib.rs 的validate_relaxed_bound方法(约 L2431-L2462)。
调用链:?修饰符从解析到校验
当降级器处理一个多态 trait 约束时,会调用lower_poly_trait_ref(L2407-L2429)。它首先解析?修饰符对应的BoundPolarity,一旦发现ast::BoundPolarity::Maybe(_)(即出现?Sized这类放宽写法),就调用validate_relaxed_bound进行合法性校验:
if let ast::BoundPolarity::Maybe(_) = modifiers.polarity { self.validate_relaxed_bound(trait_ref, *span, rbp); }核心数据结构:带去重表的RelaxedBoundPolicy
校验的关键是"当前放宽行为所处的位置策略"RelaxedBoundPolicy(L440-L455):
enum RelaxedBoundPolicy<'a> { /// 被放宽的 trait 的 DefId 及其出现位置 span。 Allowed(&'a mut FxIndexMap<DefId, Span>), Forbidden(RelaxedBoundForbiddenReason), }Allowed携带一张去重映射表,key 是"被放宽的 trait 的 DefId",value 是该放宽写法首次出现的源码位置Span。凡是在后续会进行 sized elaboration(不定长展开)的位置放宽默认约束,rustc 都会以Allowed模式降级该区域(例如 L1708、L1870、L2571 等多处以Allowed(&mut Default::default())创建全新去重表)。Forbidden用于 trait object 类型、supertrait 边界、trait 别名等不允许出现放宽写法的位置,此时触发的是另一类"relaxed bounds are not permitted"错误而非 E0203。
E0203 的精确签发逻辑
当处于Allowed分支时,validate_relaxed_bound执行如下检测(L2447-L2462):
RelaxedBoundPolicy::Allowed(dedup_map) => { // 解析失败等错误场景下 trait_def_id 才会返回 None let Some(trait_def_id) = trait_ref.trait_def_id() else { return }; let tcx = self.tcx; let err = |s| { let name = tcx.item_name(trait_def_id); tcx.dcx() .struct_span_err( vec![span, s], format!("duplicate relaxed `{name}` bounds"), ) .with_code(E0203) .emit(); }; dedup_map.entry(trait_def_id).and_modify(|&mut s| err(s)).or_insert(span); return; }这段代码的精妙之处在于:
- 通过
trait_ref.trait_def_id()取得被放宽 trait 的唯一标识DefId; - 用
dedup_map.entry(trait_def_id)查询该 trait 此前是否已在本区域被放宽过; - 若已存在(
and_modify分支),把首次出现的位置s与当前出现的位置span一并作为错误 span 上报,形成"重复放宽Trait边界"的诊断,并with_code(E0203)挂上错误码; - 若不存在(
or_insert(span)分支),记录当前位置后静默通过。
因此 E0203 的判定本质是"以同一 trait 的 DefId 为键做集合去重,第二次命中即报错"。这也解释了为何重复的必须是同一个 trait——例如?Sized + ?Iterator并不会触发 E0203(但?只能应用于Sized的稳定性限制会另外报错),因为它放松的是两个不同的默认约束。
修复方法:去掉重复的放宽写法
修复的核心原则只有一个:?Trait只写一次。
针对原文档示例,把两个?Sized合并为一个即可:
struct Good<T: ?Sized>{ inner: T }推广到其他形态:
- 泛型函数:
fn f<T: ?Sized + ?Sized>() {}改为fn f<T: ?Sized>() {}; - 关联类型:
type T: ?Sized + ?Sized;改为type T: ?Sized;; - 形如
fn f<T: ?Sized>() where T: ?Sized {}的"参数列表 + where 子句双重放宽"同样会触发重复检测,只需保留其中一处。
如果你的本意是想表达"多个互不相同的约束都放宽",记住:放宽的重复性按 trait 逐一判定,写T: ?Sized + SomeTrait(无?)是完全不同的场景,不涉及 E0203。若确有其他默认 trait 需要放宽,目前稳定版并无此能力,无需在 stable Rust 中尝试。
测试用例佐证:compiletest 如何看待这一错误
仓库中的 compiletest 用例 tests/ui/trait-bounds/duplicate-relaxed-bounds.rs 系统性地覆盖了 E0203 及相关语义,可作为排查的对照手册:
fn dupes<T: ?Sized + ?Sized + ?Iterator + ?Iterator>() {} //~^ ERROR duplicate relaxed `Sized` bounds //~| ERROR duplicate relaxed `Iterator` bounds //~| ERROR bound modifier `?` can only be applied to `Sized` //~| ERROR bound modifier `?` can only be applied to `Sized`该用例印证了以下关键事实:
- 同一泛型参数上相同 trait 的重复放宽会命中 E0203(对
Sized与Iterator各报一次); - 用例注释明确指出:早期 rustc 曾在关联类型上误报"类型参数具有多个放宽默认边界",如今已修正,
trait内的type Type: ?Sized + ?Sized;同样按重复放宽正确报错(见 L7-L11); - 两个不同trait 的放宽(如
?Sized + ?Iterator)不再是 E0203 的管辖对象,仅因?修饰符仅能作用于Sized的稳定限制而报错(L25-L26); - where 子句 + 参数列表的组合重复同样会被 E0203 捕获,说明去重表的作用域横跨同一泛型参数的完整声明(L29-L34)。
与其他"放宽边界"错误的区分
在阅读编译输出时,很容易把 E0203 与相邻的放宽边界错误混淆,此处给出判别表:
| 错误形态 | 报错位置 | 触发原因 |
|---|---|---|
| E0203 duplicate relaxed bounds | Allowed区域 | 同一 trait 被重复放宽(本文主题) |
| relaxed bounds are not permitted in {位置} | Forbidden区域 | 在 trait object 类型、supertrait 边界、trait 别名等处写了?(见RelaxedBoundForbiddenReason,L457-L466) |
bound modifier?can only be applied toSized | 稳定性检查 | 对非默认 trait 使用?(稳定版限制) |
| this relaxed bound is not permitted here | 收尾兜底 | Forbidden分支中未被gate覆盖的情况(L2508 起) |
判别方法很简单:看是不是"同一个 trait 写了多遍?"。是,则走 E0203;否,则多半属于"位置不允许"或"修饰符作用于错误目标"。
实战排查建议
- 先数
?个数:报 E0203 时,十有八九是复制粘贴泛型签名导致约束列表出现两遍相同写法,直接删除重复项即可。 - 注意 where 子句:
T: ?Sized若同时出现在尖括号内和where中,同样属于重复放宽,需合并位置。 - 关注 span 提示:rustc 的 E0203 诊断会同时标出该 trait第一次放宽与本次重复的位置,后一个高亮往往就是你该删的地方。
- 不要试图关闭该错误:E0203 无对应的
#[allow],它是语言层面的硬错误,只能通过修改源码解决。
总结
E0203 是 rustc 在 AST→HIR 降级期由 validate_relaxed_bound 依据RelaxedBoundPolicy::Allowed去重表签发的一条硬错误,语义上拒绝"对同一默认约束(如Sized)的重复放宽"。理解它的关键在于意识到?Trait是一条幂等语义应保持单次的声明:重复不会增强放宽效果,只会构成冗余并触发编译失败。删去多余的一次?Sized,代码即可恢复合法。
【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rust
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考