首页
/ Rust 编译错误 E0390 深度解析:如何在原始类型上合法实现方法与常量(inherent impl for primitive types)

Rust 编译错误 E0390 深度解析:如何在原始类型上合法实现方法与常量(inherent impl for primitive types)

2026-09-07 14:03:08作者:钟日瑜

E0390 是 rustc 在"把固有实现(inherent impl)直接写在原始类型(primitive type)上"时给出的编译错误,常见于开发者试图给 u32*mut T&T、函数指针等语言内建类型"挂"方法或关联常量。本文以 rustc 源码仓库中的官方错误文档 E0390.md 为主线,完整还原触发场景、三种修复方案,并结合 rustc_hir_analysisrustc_error_codes 的源码说明编译器究竟如何分类"原始类型"、为何 core 内部可以打破该限制,帮助读者既会改错,也读懂其背后的相干性(coherence)设计逻辑。

E0390 在讲什么

rustc 官方错误索引对 E0390 的定义只有一句话:

A method or constant was implemented on a primitive type.

即:在原始类型上实现了方法或常量。Rust 允许两种 impl

  • 固有实现(inherent impl)impl Type { ... },方法通过 Type::method() 或实例 .method() 直接调用,不依赖任何 trait;
  • Trait 实现impl Trait for Type { ... }

Rust 规定固有实现只能写在与该类型"定义于同一 crate"的代码中。对于 structenumunion 这类名义类型(nominal type),你可以在定义它们的 crate 里自由追加固有实现;而原始类型(整数、浮点、boolcharstr、数组、切片、指针、引用、函数指针、元组等)本质上是语言与 core crate 的一部分,普通 crate 无权为它们追加固有方法——这正是 E0390 的存在意义。

典型触发场景与报错原文

官方文档给出的第一个出错示例是试图给一个裸指针类型 *mut Foo 写固有实现:

struct Foo {
    x: i32
}

impl *mut Foo {}
// error: cannot define inherent `impl` for primitive types

把这段代码交给 rustc 编译,会得到(诊断定义见下文源码):

error[E0390]: cannot define inherent `impl` for primitive types
help: consider using an extension trait instead

需要特别留意:文档中该示例的内联注释文字 cannot define inherent impl for primitive types 对应的正是 diagnostics.rs 中 E0390 的主诊断文本。同样的错误也会出现在:

struct Foo {
    x: i32
}

impl fn(Foo) {} //~ ERROR E0390

为普通 u32 追加方法也一样会被拦截,例如 impl u32 { fn double(self) -> u32 { self * 2 } }ty::Uint 属于原始类型分类,详见下一节)。这也解释了为何你在标准库中看到的 impl u32 { ... } 只存在于 library/core 内部,而不是用户代码里。

编译器如何判定"原始类型":从源码看类型分流

E0390 的实际抛出点并不在解析阶段,而是在类型检查期的相干性(coherence)收集阶段。入口位于 compiler/rustc_hir_analysis/src/coherence/inherent_impls.rs,其中 crate_inherent_impls 查询会遍历当前 crate 的全部顶层条目,把所有"非 trait 的 impl"(DefKind::Impl { of_trait: false })交给 check_item 处理。

check_item 首先拿到 impl 的 self 类型(并剥掉类型别名与 pattern 包装),然后对它做一次穷举式 match,决定走哪条检查路径:

match *self_ty.kind() {
    ty::Adt(def, _) => self.check_def_id(id, self_ty, def.did()),
    ty::Foreign(did) => self.check_def_id(id, self_ty, did),
    ty::Dynamic(data, ..) if data.principal_def_id().is_some() => {
        self.check_def_id(id, self_ty, data.principal_def_id().unwrap())
    }
    ty::Dynamic(..) => {
        Err(self.tcx.dcx().emit_err(diagnostics::InherentDyn { span: item_span }))
    }
    ty::Bool
    | ty::Char
    | ty::Int(_)
    | ty::Uint(_)
    | ty::Float(_)
    | ty::Str
    | ty::Array(..)
    | ty::Slice(_)
    | ty::RawPtr(_, _)
    | ty::Ref(..)
    | ty::Never
    | ty::FnPtr(..)
    | ty::Tuple(..)
    | ty::UnsafeBinder(_) => self.check_primitive_impl(id, self_ty),
    // 投影类型、inherent/opaque 别名与泛型参数 → 走 E0118 的 InherentNominal
    ty::Param(_) => {
        Err(self.tcx.dcx().emit_err(diagnostics::InherentNominal { span: item_span }))
    }
    // ...
}

从源码可以明确看出,rustc 在判定 E0390 时所称的"原始类型"范围相当广,包括:

rustc 类型种类 对应的 Rust 语法示例
ty::Bool / ty::Char boolchar
ty::Int(_) / ty::Uint(_) i32u64 等有/无符号整数
ty::Float(_) f32f64
ty::Str str(注意不是 String
ty::Array(..) / ty::Slice(_) [T; N][T]
ty::RawPtr(_, _) *mut T*const T
ty::Ref(..) &T&mut T
ty::Never !
ty::FnPtr(..) fn(T) -> U 函数指针
ty::Tuple(..) (A, B) 元组

这些类型统统进入 check_primitive_impl。如果当前 crate 不是 core(通过 hir_rustc_coherence_is_core() 判断)且未启用内部属性豁免,就抛出 E0390。对照前面官方文档的第二个出错示例——impl &Foo 中的 &Foo 是一个 ty::Ref,同样落在这个分支里,因此该代码块在文档中被标注为 compile_fail,E0390(代码块内残留的内联注释为历史文案,不必纠结)。

诊断消息背后的结构体

E0390 的报错文本、帮助信息与"参考建议"并非硬编码在报错点,而是定义在 compiler/rustc_hir_analysis/src/diagnostics.rs,由 #[derive(Diagnostic)] 的宏统一生成:

#[derive(Diagnostic)]
#[diag("cannot define inherent `impl` for primitive types", code = E0390)]
#[help("consider using an extension trait instead")]
pub(crate) struct InherentPrimitiveTy<'a> {
    #[primary_span]
    pub span: Span,
    #[subdiagnostic]
    pub note: Option<InherentPrimitiveTyNote<'a>>,
}

#[derive(Subdiagnostic)]
#[note(
    "you could also try moving the reference to uses of `{$subty}` (such as `self`) within the implementation"
)]
pub(crate) struct InherentPrimitiveTyNote<'a> {
    pub subty: Ty<'a>,
}

值得注意的两个细节:

  • 主诊断固定附赠帮助信息 "consider using an extension trait instead"(考虑改用扩展 trait),这正对应文档的第一种修复思路;
  • note 字段只在 self 类型是引用(源码 check_primitive_implif let ty::Ref(_, subty, _) = ty.kind())时才生成,建议你"把引用挪到实现内部各参数(如 self)的使用处",对应文档的第二种修复思路。也就是说,错误消息本身已经把两条主要出路都提示给了开发者。

此外,check_primitive_impl 中还存在一条"在 core 之外给原始类型写固有实现"的变体诊断 InherentTyOutsidePrimitivediagnostics.rs),文本为 "cannot define inherent impl for primitive types outside of core",用于编译器内部启用 rustc_attrs 特性但未加豁免属性时的场景。

修复方案一:改用扩展 trait(extension trait)

官方文档推荐的第一个解决方案,是不要写固有 impl,而是定义一个 trait 并为该类型实现 trait

struct Foo {
    x: i32
}

trait Bar {
    fn bar();
}

impl Bar for *mut Foo {
    fn bar() {} // ok!
}

这是 Rust 生态中非常成熟的 extension trait 模式:为无法修改定义方(这里是语言/标准库拥有的原始类型)的类型扩展方法,只能通过自定义 trait + impl Trait for Type 实现。代价是调用时 trait 必须在作用域内(需要 use),且无法获得"固有方法"那种无须导入的调用体验——这正是 E0390 想强制约束的边界。

修复方案二:把引用"搬"进实现内部

官方文档的第二个例子更隐蔽,试图直接在引用类型 &Foo 上写固有 impl:

struct Foo;

impl &Foo { // error: no nominal type found for inherent implementation
    fn bar(self, other: Self) {}
}

其中 fn bar(self, other: Self) 的接收者与参数类型都是 &Foo 本身。文档给出的等价改写是把引用从 self 类型移到方法签名内部

struct Foo;

impl Foo {
    fn bar(&self, other: &Self) {}
}

改写后 self 类型变成了名义类型 Foo(合法),而"按引用接收"的语义通过 &self&Self 完整保留。这正好呼应了上文诊断结构体 InherentPrimitiveTyNote 中编译器为 ty::Ref 自动生成的提示文本——编译器会主动告诉你这类错误可以通过调整方法签名来解决。

修复方案三:newtype 包装(补充实践)

官方文档在 E0390 中未展开、但 rustc 错误家族普遍推荐的另一条路是 newtype 包装:把受限制的类型包进一个自定义的元组结构体,得到一个属于你自己的名义类型,从而合法获得固有实现。例如:

struct Meters(f64);

impl Meters {
    fn to_kilometers(&self) -> f64 {
        self.0 / 1000.0
    }
}

该思路在相邻错误 E0118E0116 的官方文档中也有同款示范(struct TypeWrapper<T>(T); impl<T> TypeWrapper<T> { ... }),对于需要"给数字挂单位/业务语义方法"的场景尤为实用。

何时可以"合法地"给原始类型写固有 impl

inherent_impls.rscheck_primitive_impl 可以看出,存在两类豁免:

  1. core crate 内部if !self.tcx.hir_rustc_coherence_is_core() 判断不成立时直接放行。这正是标准库能在 library/core 里对整数、str、数组等给出大量固有方法(如 str::lenu32::count_ones)的法律依据——对普通 crate 而言这些原始类型"属于" core
  2. 编译器内部 #![feature(rustc_attrs)] + 豁免属性:启用 rustc_attrs 后,若 impl 的每个关联项都标注 #[rustc_allow_incoherent_impl](类型本体再配合 #[rustc_has_incoherent_inherent_impls],参见 InherentTyOutside 诊断的 help 文本),则会被登记进 incoherent_impls 映射而非直接报错。这套机制仅面向 rustc 自身及其内部 crate,普通开发者不可用。

通过后,这类 impl 会被 simplify_type 化简,存入 CrateInherentImpls::incoherent_impls(按 SimplifiedType 索引),供后续方法查找使用;而普通名义类型的固有 impl 则按本地 DefId 存入 inherent_impls 映射,通过按需查询 tcx.inherent_impls(def_id) 获取。

与相近错误 E0116 / E0118 / E0785 的区分

E0390 容易与同属"固有 impl 合法性检查"的另外几个错误混淆,它们各自的"不合格 self 类型"不同,修复思路也不同:

错误码 自我类型不合格的原因 主要修复手段 本仓库文档
E0390 self 类型是原始类型(指针、引用、整数、元组、函数指针等) 扩展 trait / 把引用搬进签名 / newtype E0390.md
E0118 self 类型是泛型参数、投影/别名类型等"非名义类型",如 impl<T> T 改用 trait 或 newtype 包装 E0118.md
E0116 self 类型是其他 crate 定义的 ADT trait 实现或 newtype(孤儿规则) E0116.md
E0785 对无 principal 的 dyn(纯 auto trait 对象)写固有 impl 定义并实现一个普通 trait E0785.md

check_item 的 match 结构可以清楚地看到这几条路径的分界:ty::Adt/ty::Foreign/带 principal 的 ty::Dynamiccheck_def_id(其中跨 crate 情形触发 E0116),无 principal 的 ty::Dynamic 触发 E0785,ty::Param 与投影/别名类型触发 E0118(InherentNominal,即 E0118 文档中 "no nominal type found for inherent implementation" 那条),而原始类型大家庭统一落入 E0390。

验证方式

仓库在 tests/ui/error-codes/E0390.rs 提供了 E0390 的回归测试,一次性覆盖两个触发面:

struct Foo {
    x: i32
}

impl *mut Foo {} //~ ERROR E0390

impl fn(Foo) {} //~ ERROR E0390

fn main() {
}

该文件通过 compiletest 以 //~ ERROR E0390 断言对应行必须产生 E0390。你也可以本地自测:把上面的 impl fn(Foo) {} 换成 impl u32 { fn double(self) -> u32 { self * 2 } }rustc 编译,会看到完全相同的 error[E0390] 与 "consider using an extension trait instead" 帮助信息;随后按方案一改写为 trait Double { fn double(self) -> u32; }impl Double for u32 { ... } 即可通过编译。

小结

E0390 的实质是 Rust 相干性规则对"方法归属权"的强制表达:原始类型的固有方法只能由它们的所有者(core)提供。面对该错误时,按编译器提示顺次尝试三种方案即可:用扩展 trait 表达"为类型扩展能力"、把引用从 self 类型挪进方法签名、或用 newtype 建立自己的名义类型。理解 inherent_impls.rs 中那张穷举式 self 类型分发表,也能顺带厘清 E0390 与 E0116、E0118、E0785 之间的边界,从而在更复杂的相干性报错面前举一反三。

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

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
898
5.82 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
921
1.84 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.8 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
531
596
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.02 K
519
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
391