首页
/ Rust 编译器错误 E0120 详解:为什么不能对 trait 对象或引用实现 Drop,以及两种合规的包装器方案

Rust 编译器错误 E0120 详解:为什么不能对 trait 对象或引用实现 Drop,以及两种合规的包装器方案

2026-09-06 15:33:41作者:秋泉律Samson

本文围绕 Rust 编译器的错误码 E0120(Drop was implemented on a trait object or reference)展开,基于错误码文档 E0120.md 的完整规则说明与官方示例,并结合 rustc 源码中该诊断的触发路径 coherence/builtin.rs 与诊断定义 diagnostics.rs。读完后,你将准确理解 E0120 的判定边界(哪些 self 类型被允许、哪些被拒绝)、遇到该错误时的两种可编译变通写法,以及编译器在内置 trait 检查阶段是如何拦截非法 Drop 实现的。

E0120 错误说明

错误码文档 E0120.md 对该错误的定义是:

Drop was implemented on a trait object or reference, which is not allowed; only structs, enums, and unions can implement Drop.

即:Drop 被实现在了 trait 对象或引用上,这是不允许的;只有 struct、enum、union 才能实现 Drop

这一点在编译器源码中有直接对应。rustc 的诊断定义为(见 diagnostics.rs):

#[derive(Diagnostic)]
#[diag("the `{$trait_}` trait may only be implemented for local structs, enums, and unions", code = E0120)]
pub(crate) struct DropImplOnWrongItem {
    #[primary_span]
    #[label("must be a struct, enum, or union in the current crate")]
    pub span: Span,
    pub trait_: Symbol,
}

从诊断文案可以看出完整规则比标题更严格,共有两层限制:

  1. self 类型必须是 ADT(struct / enum / union);trait 对象(dyn MyTrait)、引用(&mut Concrete)、裸指针、原始数组等都不是 ADT,一律被拒;
  2. 该 ADT 必须是当前 crate 中定义的类型is_local())。因此你甚至无法为 std 或其他 crate 里的类型补一个 Drop 实现——这是 Rust 孤儿规则在析构语义上的体现。

这条规则背后的工程原因是:Drop 是运行时析构钩子,编译器需要为每个类型确定性地生成析构调用(drop glue)。如果允许对 trait 对象或引用定义 Drop,就无法确定析构逻辑与具体类型布局、所有权语义的对应关系,会破坏内存安全保证。

触发 E0120 的典型写法

错误码文档给出了两个官方错误示例(原文标注为 compile_fail,E0120),两者分别对应最常见的误用场景。

场景一:把 Drop 实现到 trait 本身

trait MyTrait {}

impl Drop for MyTrait {
    fn drop(&mut self) {}
}

这里的意图通常是“为所有实现 MyTrait 的类型统一提供析构逻辑”,但 MyTrait 并不是一个可存储值的类型,它只是方法的集合。impl Drop for MyTrait 会被编译器理解为对 trait 对象 dyn MyTrait 实现 Drop,直接触发 E0120。

场景二:把 Drop 实现到引用上

struct Concrete {}

impl Drop for &'_ mut Concrete  {
    fn drop(&mut self) {}
}

这里的意图通常是“当最后这个可变引用消失时做一些清理”。但引用本身不拥有数据,其生命周期语义由借用检查器管理,不能携带自定义析构。&mut Concrete 不是 struct/enum/union,同样触发 E0120。

两种写法共同的判定依据都来自编译器对 Drop 实现的专门检查(下一节详述):self 类型既不是本地 ADT,也不是 ty::Error,于是诊断 DropImplOnWrongItem 被发射,主 span 落在 impl 的 self 类型位置。

变通方案一:泛型包装器 struct(文档官方推荐)

错误码文档给出的第一个 workaround:创建带泛型参数的包装 struct,给类型参数加上 trait 约束,然后在包装器上实现 Drop

trait MyTrait {}
struct MyWrapper<T: MyTrait> { foo: T }

impl <T: MyTrait> Drop for MyWrapper<T> {
    fn drop(&mut self) {}
}

这段代码可以正常编译,原因对照源码判定条件逐条成立:

  • self 类型 MyWrapper<T> 是一个 struct,满足 ADT 要求;
  • 它定义在当前 crate,满足 is_local() 要求;
  • 泛型参数 T 被约束为 T: MyTrait,包装器因此“只代表”实现了该 trait 的具体类型,析构时机清晰:MyWrapper<T> 值离开作用域或显式 drop 时,MyWrapper<T>drop 先执行,随后字段 foo: TT 自身的析构语义处理。

适用场景:你确实需要在持有某个实现了特定 trait 的值时附加自定义清理逻辑(如释放句柄、关闭描述符、归还连接池资源),并且你控制着值的封装形态。

变通方案二:包装 trait 对象引用

错误码文档的第二个方案是让 Drop 包装器直接持有 trait 对象:

trait MyTrait {}

// or Box<dyn MyTrait>, if you wanted an owned trait object
struct MyWrapper<'a> { foo: &'a dyn MyTrait }

impl <'a> Drop for MyWrapper<'a> {
    fn drop(&mut self) {}
}

与方案一的区别在于字段类型:这里持有的是引用类型的 trait 对象 &'a dyn MyTrait(文档注释同时指出,若需要拥有 trait 对象本体,可改用 Box<dyn MyTrait>)。MyWrapper<'a> 本身仍是本 crate 的 struct,因此满足 E0120 的判定条件;而“对 trait 对象做清理”的语义由 drop 方法内部逻辑承担。

适用场景:你拿到的值已经是 &dyn Trait 这类动态分发值,无法也不打算改变上游的类型形态,只需在自己的作用域边界上挂一个清理钩子。

两个方案的取舍小结

维度 方案一(泛型包装) 方案二(trait 对象包装)
字段类型 T: MyTrait,静态分发 &'a dyn MyTrait / Box<dyn MyTrait>,动态分发
值语义 零成本包装具体类型 携带引用/堆指针,多一层间接
约束 需要 T 实现 trait 只要求能通过 trait 对象访问
典型用途 句柄、资源封装 对已有 dyn Trait 值挂清理逻辑

无论选哪种,关键都在于:Drop 的 self 类型必须收敛为一个本 crate 的具名 struct/enum/union,这正是 E0120 检查的核心。

编译器内部视角:E0120 是如何被检出的

E0120 并非来自一般的 trait 一致性检查,而是来自对内置(lang item)trait 的专项检查。检查路径如下:

  1. 入口分发。在 coherence/builtin.rs 中,check_trait 函数按 lang item 分派各个内置 trait 的额外属性检查,其中 LangItem::DropLangItem::AsyncDrop 都走 visit_implementation_of_drop
match tcx.as_lang_item(trait_def_id) {
    Some(LangItem::Drop) => visit_implementation_of_drop(&checker),
    Some(LangItem::AsyncDrop) => visit_implementation_of_drop(&checker),
    Some(LangItem::Copy) => visit_implementation_of_copy(&checker),
    // ... 其他内置 trait
    _ => Ok(()),
}
  1. 判定逻辑visit_implementation_of_dropbuiltin.rs)的实现非常简洁:
fn visit_implementation_of_drop(checker: &Checker<'_>) -> Result<(), ErrorGuaranteed> {
    let tcx = checker.tcx;
    let impl_did = checker.impl_def_id;
    // Destructors only work on local ADT types.
    match checker.impl_header.trait_ref.instantiate_identity().skip_norm_wip().self_ty().kind() {
        ty::Adt(def, _) if def.did().is_local() => return Ok(()),
        ty::Error(_) => return Ok(()),
        _ => {}
    }

    let impl_ = tcx.hir_expect_item(impl_did).expect_impl();

    Err(tcx.dcx().emit_err(diagnostics::DropImplOnWrongItem {
        span: impl_.self_ty.span,
        trait_: tcx.item_name(checker.impl_header.trait_ref.skip_binder().def_id),
    }))
}

源码注释直接点明设计意图:Destructors only work on local ADT types.。判定逻辑分三层:

  • self 类型是 ty::Adt 且定义于当前 crate(def.did().is_local())→ 检查通过,返回 Ok(())
  • self 类型是 ty::Error(类型检查已失败)→ 静默通过,避免级联误报;
  • 其余一切类型(trait 对象、引用、裸指针、元组、外部 crate 的 ADT 等)→ 发射 DropImplOnWrongItem 错误,主 span 指向 impl 头部的 self 类型,诊断文案为 the \Drop` trait may only be implemented for local structs, enums, and unions,附带 label must be a struct, enum, or union in the current crate`。
  1. 注意覆盖面。从上述 match 分支可以推断,凡是不满足“本地 ADT”的实现都会报 E0120,这比错误码文档标题“trait object or reference”描述得更为宽泛——例如对 i32[T]、外部 crate 的 struct 实现 Drop,走的是同一条错误路径。

相关约束与排查建议

  • Copy 检查同源check_trait 分派表中的其他分支(CopyUnpinCoerceUnsized 等)与 Drop 检查共用同一套基础设施,同文件中的 visit_implementation_of_copy 会对非 ADT 类型报 CopyImplOnNonAdt(见 builtin.rs)。如果你遇到的是“对引用/非 ADT 类型实现 Copy 被拒”的报错,属于相邻但不同的诊断,排查思路可类比本节。
  • 遇到 E0120 时的定位动作
    1. 看报错主 span 指向的 self 类型,确认它属于 trait、引用还是其他非 ADT 类型;
    2. 确认目标 ADT 是否定义在当前 crate(跨 crate 补 Drop 同样会被本检查拒绝);
    3. 若必须对动态类型或引用挂清理逻辑,套用前文的包装器方案:把清理逻辑上移到包装 struct 的 drop 方法中,而不是直接修改 impl 头。
  • 文档与源码一致性:错误码文档 E0120.md 中的两个错误示例与两个 workaround 示例,与源码判定条件一一对应——两个反例恰好落在 visit_implementation_of_drop 的拒绝分支(trait、引用),两个变通例的 self 类型都是本 crate 的 struct,恰好命中 Ok(()) 分支。

小结

E0120 的本质是一条编译器对析构钩子的硬性边界:Drop 只能由当前 crate 中定义的 struct、enum、union 实现。触发它几乎总是因为把 impl Drop 的 self 类型写成了 trait 或引用;修复手段不是绕过检查,而是把清理逻辑装进一个具名包装类型——用泛型包装器(静态分发)或 trait 对象包装器(动态分发)二选一。这条规则及其检查实现(builtin.rsvisit_implementation_of_drop)保证了 rustc 生成的析构 glue 始终与具体类型布局一一对应,是 Rust 内存安全模型在析构阶段的具体体现。

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