深入解析 Rust 编译错误 E0618:Attempted to call something which isn't a function nor a method
【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rust
本文聚焦于 rustc 的类型检查诊断体系,系统讲解 E0618(attempted to call something which isn't a function nor a method)的触发场景、rustc 内部生成路径与可执行的修复方案。你将理解为什么X::Entry()与0i32()会被编译器拒绝、该错误如何从类型检查器(typeck)的 callee 解析流程中产生,并能针对单位变体、整数、方法调用形态误写等场景给出正确的改写代码。
错误定义:只有函数与方法可以被()调用
E0618 是 rustc 在“对既非函数也非方法的表达式使用调用语法()”时报出的编译期错误。Rust 中expr(args)这一语法形态只能作用于可调用对象(callable),即函数、函数指针、实现Fn/FnMut/FnOncetrait 的闭包与结构体,以及元组结构体构造器/元组变体构造器等;其他类型即便语法上写得像“调用”,类型检查阶段也会将其拒绝。
本错误的官方说明位于 compiler/rustc_error_codes/src/error_codes/E0618.md,其注册的错误码行为由 rustc_hir_typeck crate 实现(见下文源码分析)。
触发场景与最小复现
官方文档给出的典型错误代码如下:
enum X { Entry, } X::Entry(); // error: expected function, tuple struct or tuple variant, // found `X::Entry` // Or even simpler: let x = 0i32; x(); // error: expected function, tuple struct or tuple variant, found `i32`第一个例子中,X::Entry是枚举的单位变体(unit variant),它只是一个值,不携带任何数据,也不具备可调用语义;对它使用()调用语法自然不合法。第二个例子中x是i32类型的普通整数变量,rustc 明确将其诊断信息格式化为expected function, tuple struct or tuple variant, found \i32`——其中found` 部分如实描述了被误当作函数去调用的“真实身份”。
合法的调用示例
与之相对,只有真正的可调用项才允许接()。官方文档补充了最基础的对照示例:
// We declare a function: fn i_am_a_function() {} // And we call it: i_am_a_function();这里i_am_a_function是一个零参数函数,调用i_am_a_function()合法。同理,带字段的枚举变体Entry(i32)、元组结构体struct P(i32);、实现了Fn系列 trait 的类型,都可以通过()调用。
编译器如何诊断:从 callee 解析到 E0618
要真正理解 E0618,需要追溯到 rustc 对调用表达式的类型检查流程。对call_expr的检查集中在 compiler/rustc_hir_typeck/src/callee.rs 的check_call相关逻辑中:编译器会先对被调用表达式(callee)执行自动解引用(autoderef)并尝试解析其可调用签名。当解析完全失败(得到None,即既非内置调用、也非重载的Fn调用、更非闭包调用)时,走兜底分支:
- 若被调用方是形如
foo(...)的路径,rustc 会先尝试窃取/改写诊断,判断能否将foo(a)改写为a.foo()(方法调用形态修正,见 callee.rs); - 随后调用
report_invalid_callee,最终在 callee.rs 内部创建 E0618 诊断,并把它作为类型错误传播(Ty::new_error),使后续编译阶段不再基于错误类型继续推导。
诊断体本身的定义在 compiler/rustc_hir_typeck/src/diagnostics.rs:
#[derive(Diagnostic)] #[diag("expected function, found {$found}", code = E0618)] pub(crate) struct InvalidCallee { #[primary_span] pub span: Span, pub found: String, }found字段就是错误信息中被调用对象的“真实身份”字符串,它由 callee.rs 组装:若被调用对象是单位结构体或单位变体,则格式化为struct \path`或enum variant `path`;否则格式化为对应类型`i32`` 之类的类型名。这就是 E0618 报错文案的最终来源。
rustc 为 E0618 提供的“加分”诊断建议
从源码看,report_invalid_callee不仅给出主错误,还会根据具体情况附加有参考价值的子诊断:
- 单位变体/单位结构体的括号删除建议:当被调用者是
Ctor(kind, CtorKind::Const)且调用不含任何实参时,rustc 认为极可能是“误把构造写法当调用”,会给出移除括号的机器可应用(MachineApplicable)建议,提示"X::Entryis a unit variant, and does not take parentheses to be constructed",对应代码位于 callee.rs。 - 跨行调用的缺失分号建议:若调用表达式跨多行、且被调用对象本身又是一个调用表达式,编译器会怀疑漏了语句结束分号,建议在合适位置补
;(对应 issue #51055/#106515 场景),见 callee.rs。 - 定义处标注:编译器会尝试在函数/局部变量/类型路径的定义处追加 span label,如
\x` has type `i32`、`Entry` defined here`,帮助开发者快速定位问题源头(callee.rs)。 - 被遮蔽的同名函数提示:若
x()中的x是局部绑定,而当前 crate 恰有同名的函数项,rustc 会额外指出该函数定义位置并提示“它被局部绑定遮蔽了”(callee.rs)。
实战修复对照表
结合 E0618 的常见误写,可按下列策略修改代码:
| 场景 | 错误写法 | 修复方式 |
|---|---|---|
| 单位枚举变体被误当函数 | X::Entry() | 去掉括号,改为X::Entry(作为值使用);若确需带负载数据应改声明为元组变体Entry(i32)后以X::Entry(v)构造 |
| 整数/普通值变量被调用 | let x = 0i32; x(); | 去掉(),变量本身不承载任何可执行逻辑;若意图是执行某个函数,改为调用真正的函数i_am_a_function() |
| 方法调用写成了自由调用 | strlen(s) | 若strlen实为s的方法,改为方法语法s.strlen();rustc 给出的提示同样会推荐这一改写 |
| 上一语句漏分号导致粘连调用 | 跨行将表达式“拼接”成调用 | 按诊断建议在合适位置补分号结束前一语句,再另起调用 |
| 局部绑定遮蔽同名函数 | let foo = 1; foo();且另有fn foo() | 依据“同名函数被遮蔽”的提示,重命名局部变量或显式路径调用函数 |
相关测试与进一步阅读
读者可结合 error code 文档正文与类型检查源码核对以下资料:
- 错误说明原文:compiler/rustc_error_codes/src/error_codes/E0618.md
- 诊断体(错误文案与码绑定):diagnostics.rs
- 检查触发与子建议(自动解引用、方法化改写、单位变体去括号、缺失分号等):callee.rs
- 编译错误代码总览目录位于 compiler/rustc_error_codes,可通过
x.py test tests/ui一类的测试流程观察相关 ui 测试输出。
小结
E0618 是 rustc 在类型检查阶段对“非法调用非函数对象”的统一答复:Rust 的()调用语法只服务于函数、可调用 trait 实现者以及带元组形式的构造器/变体。本文既给出了最小复现与修复对照,也溯源了错误在rustc_hir_typeckcallee 解析、InvalidCallee诊断定义中的完整生成链路——理解这一过程后,当遇到形形色色的“不能调用”场景时,便能依据诊断中found的真实类型快速定位并改写为合法的 Rust 代码。
【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rust
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考