ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Rust 编译器 E0118 错误详解:为什么 impl 必须作用于命名类型(nominal type)

Rust 编译器 E0118 错误详解:为什么 impl 必须作用于命名类型(nominal type) Rust 编译器 E0118 错误详解为什么 impl 必须作用于命名类型nominal type【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rust本文基于 Rust 仓库中的官方错误码文档 E0118.md讲解错误E0118: no nominal type found for inherent implementation的触发条件、两种官方修复方案trait 实现与新类型封装并结合 coherence/inherent_impls.rs 中的源码检查逻辑说明编译器是如何判定一个impl的 self 类型是否合法的以及它与 E0390、E0116 等相关错误的边界区别。一、E0118 是什么impl 只能挂在命名类型上E0118 的完整诊断信息为error[E0118]: no nominal type found for inherent implementation note: either implement a trait on it or create a newtype to wrap it instead根据 E0118.md 的定义该错误出现在**你为不是 struct、enum、union 或 trait object 的类型定义了 inherent implementation固有 impl即impl X { ... }形式**时。最典型的触发场景是为类型参数T直接写 implimplT T { // error: no nominal type found for inherent implementation fn get_state(self) - String { // ... } }这条错误码对应的 UI 测试用例 tests/ui/error-codes/E0118.rs 与预期输出 E0118.stderr 精确复现了上面的场景编译器输出的诊断如下error[E0118]: no nominal type found for inherent implementation -- E0118.rs:1:1 | LL | implT T { | ^^^^^^^^^ impl requires a nominal type | note: either implement a trait on it or create a newtype to wrap it instead这里的关键概念是nominal type名义/命名类型。从源码结构看一个合法的 inherent impl 的 self 类型必须能在impls_map中注册到一个具体的类型定义 IDDefId上——因为编译器查询某个类型有哪些固有方法时走的是tcx.inherent_impls(def_id)这条以类型DefId为键的路径见 inherent_impls.rs 的inherent_impls函数。而泛型参数T、trait 投影T as Tr::Assoc这类类型根本没有自己的DefId无法进入这张映射表所以编译器直接拒绝这正是 no nominal type found 的字面含义。为什么这么设计inherent impl 是类型自身的一部分它的查找优先级高于 trait 方法写obj.method()时编译器先看固有方法再看 trait 方法。如果把implT T这样的写法放开意味着任意 crate 都能给任意类型凭空添加方法这些方法会静默地遮蔽同名 trait 方法破坏名称解析的确定性。因此语言层面规定固有 impl 必须落在一个具体的命名类型上跨 crate 还受孤儿规则orphan rules约束。二、官方修复方案一实现一个 traitE0118.md 给出的第一种修复方式是不要直接给T写固有 impl而是定义一个 trait 并在T上实现它// we create a trait here trait LiveLongAndProsper { fn get_state(self) - String; } // and now you can implement it on T implT LiveLongAndProsper for T { fn get_state(self) - String { Hes dead, Jim!.to_owned() } }注意implT LiveLongAndProsper for T与implT T的本质区别前者实现的目标是traittrait 解析遵循 Rust 的方法查找规则按T: LiveLongAndProsper约束生效不存在给类型凭空添加固有成员的问题。实际使用中常见的变体是为T加上约束例如implT: AsRefstr MyTrait for T这属于 trait impl 的正常用法。三、官方修复方案二newtype新类型封装文档给出的第二种修复方式是newtype 包装——用一个单字段元组结构体把目标类型包起来然后在包装类型上定义固有方法。文档原话newtype 是一个 wrapping tuple-struct例如struct NewType(Foo)中NewType就是Foo的 newtype。struct TypeWrapperT(T); implT TypeWrapperT { fn get_state(self) - String { Fascinating!.to_owned() } }TypeWrapperT是一个真正的 struct有DefId因此implT TypeWrapperT { ... }完全合法其固有方法会按正常的固有方法查找路径被注册和解析。newtype 包装同时也是绕过孤儿规则E0116处理外部类型时的标准手法编译器在 diagnostics.rs 中给出的 E0116 帮助信息正是consider defining a trait and implementing it for the type or using a newtype wrapper like struct MyType(ExternalType); and implement it——两种思路与 E0118 的 note 建议完全一致。两种方案如何选择需要把方法挂到调用方可见的原始类型上调用t.get_state()时t本身是T用 trait 方案。方法只服务于内部表示、可以接受多一层包装用 newtype 方案它还附带了类型区分TypeWrapperT与T不是同一类型防止误用和 ABI 不变零成本包装的优点。四、编译器源码视角E0118 在哪里被触发E0118 的诊断定义位于 diagnostics.rs#[derive(Diagnostic)] #[diag(no nominal type found for inherent implementation, code E0118)] #[note(either implement a trait on it or create a newtype to wrap it instead)] pub(crate) struct InherentNominal { #[primary_span] #[label(impl requires a nominal type)] pub span: Span, }触发它的检查逻辑在 inherent_impls.rs 的crate_inherent_impls查询中该查询遍历当前 crate 的所有顶层项tcx.hir_free_items()对每个DefKind::Impl { of_trait: false }的固有 impl 调用check_item第 168 行起按 self 类型的种类self_ty.kind()分派self 类型种类处理路径结果ty::Adtstruct/enum/union、ty::Foreigncheck_def_id合法注册进impls_map.inherent_implsty::Dynamic且含 principal traitcheck_def_idtrait object 合法ty::Dynamic仅 auto trait直接报错InherentDynE0785基本类型Bool/Int/Ref/Tuple等check_primitive_impl非core中报 E0390投影/固有别名/Opaque、类型参数ty::Param直接报错InherentNominalE0118FnDef/闭包/协程等bug!理论上不会在 impl 头出现关键分支见 inherent_impls.rs 第 206-215 行ty::Alias(_, ty::AliasTy { kind: ty::Projection { .. } | ty::Inherent { .. } | ty::Opaque { .. }, .. }) | ty::Param(_) { Err(self.tcx.dcx().emit_err(diagnostics::InherentNominal { span: item_span })) }即泛型参数ty::Param和_::Assoc类别名ty::Alias中的 Projection/Inherent/Opaque统一触发 E0118。这也解释了为何implT T { ... }和implT: Tr T as Tr::Item { ... }都会得到同一条错误——它们都不是 nominal type。五、与相关错误的区分阅读源码后可以清晰划出 E0118 与三个相邻错误的边界避免混淆E0390不能为基本类型定义固有 implimpl i32 { ... }、impl str { ... }这类基本类型 impl 走的是check_primitive_impl分支只有core等系统 crate带rustc_coherence_is_core属性可以写。普通用户会得到 cannot define inherentimplfor primitive types 并建议使用 extension traitE0390 诊断定义。E0116孤儿规则 / 跨 crate 固有 impl为别的 crate 定义的类型写固有 impl走check_def_id中ty_def_id.as_local()为None的分支报 cannot define inherentimplfor a type outside of the crate where the type is defined修复建议同样是 trait 或 newtype第 1250-1264 行。E0785不能为 dyn auto trait 定义固有 implimpl dyn Send { ... }这种只有 auto trait 的 trait object 触发InherentDyn第 1303-1310 行。一句话总结三者的判定顺序先看 self 类型是不是 nominal type不是则 E0118是 nominal 但属于基本类型则 E0390是外部 crate 的类型则 E0116。六、小结E0118 的本质是固有 impl 的 self 类型必须是 struct、enum、union 或 trait object 这类有定义 ID 的命名类型泛型参数T、关联类型投影等匿名类型没有注册位置编译器直接拒绝。官方给出的两条修复路径实现 trait保留对原类型的调用方式或newtype 元组结构体包装获得独立的命名类型。触发点源码位于 rustc_hir_analysis 的 coherence 检查诊断文本定义于 rustc_hir_analysis/src/diagnostics.rs官方错误码文档见 E0118.md回归测试见 tests/ui/error-codes/E0118.rs。当你遇到no nominal type found for inherent implementation时对照本文的判定表和两种修复模式基本可以一次改对。【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rust创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表