ARTICLE DETAIL

资讯详情

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

Rust 编译器错误 E0059 深度解析:Fn 家族 trait 的类型参数为什么必须是元组

Rust 编译器错误 E0059 深度解析:Fn 家族 trait 的类型参数为什么必须是元组 Rust 编译器错误 E0059 深度解析Fn 家族 trait 的类型参数为什么必须是元组【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rust本文围绕 Rust 编译器错误 E0059type parameter to bareFntrait must be a tuple展开解释内置函数 traitFn/FnMut/FnOnce为何对参数列表采用元组编码、角括号记法与圆括号记法的本质差异以及如何正确修复该错误。读完本文你将能在出现 E0059 时快速定位到 trait bound 写法问题并从 错误代码文档 与 trait 定义源码 两个层面理解编译器报错的完整链条。一、E0059 的触发场景函数 trait 的元组编码官方错误代码文档 E0059.md 给出的核心结论是内置的函数 trait 是关于函数参数元组a tuple of the function arguments进行泛型的。如果不用圆括号记法Fn(T) - U而是使用角括号记法Fn(T,), Output U来标注函数 trait那么其类型参数必须是一个元组类型。否则调用表达式call notation将无法使用并且闭包也不会实现该 trait。这句话揭示了 E0059 的根源。可以查阅 library/core/src/ops/function.rs 中三个函数 trait 的真实定义// library/core/src/ops/function.rs L76 pub const trait FnArgs: Tuple: [const] FnMutArgs { extern rust-call fn call(self, args: Args) - Self::Output; } // L163 pub const trait FnMutArgs: Tuple: FnOnceArgs { extern rust-call fn call_mut(mut self, args: Args) - Self::Output; } // L242 pub const trait FnOnceArgs: Tuple { type Output; extern rust-call fn call_once(self, args: Args) - Self::Output; }三个 trait 都只有一个类型参数Args且该参数带有Args: Tuple的约束Tuple即std::marker::Tuple是不稳定特征。也就是说从类型系统的角度Fn并不是接受任意多个参数的 trait而是接受一个类型为元组或单元的参数的 trait。当你写Fni32时编译器会检查i32: Tuple——这个约束无法满足于是报错。同时注意这些 trait 上都标注了#[rustc_paren_sugar]属性。从源码结构看这是编译器对圆括号记法Fn(i32) - u32做语法糖展开的标志Fn(i32)会被归一化为Fn(i32,), Output ...即自动帮你把参数包成元组。因此日常使用圆括号记法时i32会被编译器自动处理成(i32,)永远不会遇到 E0059只有显式写出角括号记法时元组包装的责任才落在你身上。二、最小复现示例与修复方法错误文档给出的最小复现示例需要 nightly 特性门控#![feature(unboxed_closures)] fn fooF: Fni32(f: F) - F::Output { f(3) }这段代码里Fni32的角括号内直接写了裸类型i32而非元组触发 E0059。仓库中的官方错误测试 tests/ui/error-codes/E0059.rs 与这段示例一致其期望输出 tests/ui/error-codes/E0059.stderr 显示实际上会产生三个相互关联的错误error[E0059]: type parameter to bare Fn trait must be a tuple -- $DIR/E0059.rs:3:11 | LL | fn fooF: Fni32(f: F) - F::Output { f(3) } | ^^^^^^^ the nightly-only, unstable trait std::marker::Tuple is not implemented for i32 | note: required by a bound in Fn -- $SRC_DIR/core/src/ops/function.rs:LL:COL error[E0277]: i32 is not a tuple -- $DIR/E0059.rs:3:41 | LL | fn fooF: Fni32(f: F) - F::Output { f(3) } | ^^^^ the nightly-only, unstable trait std::marker::Tuple is not implemented for i32 error[E0059]: cannot use call notation; the first type parameter for the function trait is neither a tuple nor unit -- $DIR/E0059.rs:3:41可以看到 E0059 实际有两种报错形态分别来自编译器的两个不同阶段形态 1trait bound 检查阶段trait 的裸类型参数必须是元组——当WhereClause中出现对函数 trait 的角括号约束且参数不是元组时由 compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs 中的add_tuple_trait_message发出// fulfillment_errors.rs L3401-L3406 ObligationCauseCode::WhereClause(def_id, _) if self.tcx.is_fn_trait(*def_id) { err.code(E0059); err.primary_message(format!( type parameter to bare {} trait must be a tuple, self.tcx.def_path_str(*def_id) )); }这里self.tcx.is_fn_trait(*def_id)正是判定该 trait 属于 Fn 家族的依据随后诊断文本中引用的Tupletrait 未实现提示the nightly-only, unstable traitstd::marker::Tupleis not implemented fori32直接对应 function.rs 中Args: Tuple这条 bound。形态 2函数调用检查阶段无法使用调用记法函数 trait 的第一个类型参数既不是元组也不是单元——当你真正写出f(3)这样的调用表达式时compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs 会校验调用目标解析出的参数类型若既不是元组也不是()则发出该错误// checks.rs L752-L761 let guar if tuple_arguments TupleAllCallArgs !matches!(tuple_type.kind(), ty::Tuple(_)) { let guar struct_span_code_err!( self.dcx(), call_span, E0059, cannot use call notation; the first type parameter \ for the function trait is neither a tuple nor unit ) .emit(); Some(guar) }这说明 E0059 不只是 trait bound 层面的约束而是贯穿声明约束与实际调用两个环节的一致检查即使 bound 检查被绕过调用表达式本身也会被拦截。修复方式文档给出的修复是把参数类型包进元组#![feature(unboxed_closures)] fn fooF: Fn(i32,)(f: F) - F::Output { f(3) }Fn(i32,)现在满足Args: Tuple(i32,)是一元元组实现了Tuple调用记法也随之恢复可用。仓库测试 tests/ui/suggestions/fn-trait-notation.rs 中还展示了三种写法的对照均为 nightly 特性下的角括号形式Fni32, Output i32参数未包元组触发 E0059Fn(i32, i32,), Output (i32, i32)二元参数元组写法正确Fn(i32,), Output i32一元参数元组写法正确。该测试的期望输出中还展示了编译器会针对角括号形式给出help: use parenthetical notation instead: Fn(i32) - i32的建议即优先推荐稳定、可读的圆括号记法。三、细节辨析一元元组为什么必须写逗号错误文档特别强调了一点容易在实践中被忽略(T,)永远表示包含一个T类型元素的一元元组的类型。这个逗号对于语法消歧是必需的。原因是(T)在 Rust 语法中就是T本身括号仅用于分组不改变类型(T, U)才是二元元组。编译器需要逗号来区分一元元组与被括号包裹的裸类型。可以结合 tests/ui/suggestions/fn-trait-notation.stderr 中G: Fn(i32, i32, ), Output (i32, i32)的写法体会多参数元组末尾同样保留逗号风格保证元组标记的一致性与可解析性。如果省略逗号写成Fni32, Output i32编译器把它理解为Args i32i32: Tuple不成立于是产生本文所述的 E0059。四、进阶场景泛型元组参数下的调用限制E0059 的第二种形态调用记法不可用在更泛化的场景下才会独立出现。测试 tests/ui/unboxed-closures/non-tupled-call.rs 演示了这种情况#![feature(fn_traits, unboxed_closures, tuple_trait)] use std::default::Default; use std::marker::Tuple; fn wrapP: Tuple Default, T(func: impl FnP, Output T) { let x: P Default::default(); func(x); }这里P是一个满足Tuple的泛型参数bound 检查本身通过了P: Tuple成立但func(x)依然触发error[E0059]: cannot use call notation; the first type parameter for the function trait is neither a tuple nor unit -- $DIR/non-tupled-call.rs:9:5 | LL | func(x); | ^^^^^^^从 checks.rs 的判定逻辑看编译器在此处要求参数类型是具体的ty::Tuple(_)结构或单元类型才能展开把实参打包成元组传给Args的调用语义对未实例化的类型变量P它无法在编译期展开打包逻辑因此报cannot use call notation。测试文件中的注释也明确提示正确写法是显式调用func.call(x)而非依赖调用语法糖。这一场景提示当你以完全泛型的方式处理元组类型的参数时应调用FnOnce::call_once等显式方法而不是()语法。五、实践建议与小结结合 E0059.md 与仓库源码可以归纳出以下要点优先使用圆括号记法。Fn(i32) - u32、FnMut(i32, str) - u32是稳定 API编译器通过#[rustc_paren_sugar]自动完成元组包装从源头规避 E0059。角括号记法下必须手写元组。Fn(i32,)、FnMut(i32, str,)一元参数不可省略逗号角括号形式依赖unboxed_closures/fn_traits等 nightly 特性且其精确参数格式仍可能变化相关测试 tests/ui/suggestions/fn-trait-notation.rs 中会伴随 E0658 提示。理解报错的两层来源。type parameter must be a tuple 来自 trait 约束检查fulfillment_errors.rscannot use call notation 来自调用表达式检查checks.rs两者都可能标记 E0059伴随出现的E0277: i32 is not a tuple是同根因的连带诊断。泛型元组参数慎用()语法糖。参数类型是泛型P: Tuple时调用表达式不合法需改用FnOnce::call_once(x)等显式方法参考 non-tupled-call.rs。E0059 表面上是一个元组少写一个逗号的笔误级错误实质上反映了 Rust 函数 trait 的设计调用运算符被统一建模为对单个元组参数求值而稳定 API 用rustc_paren_sugar语法糖把这一建模细节对用户隐藏。理解这一点后无论是手写 trait bound 还是设计泛型高阶函数HOF你都能写出与编译器类型系统完全对齐的代码。【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rust创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表