rust实现获取函数名
你也许有这样一个疑问,为啥rust不提供获取函数名的函数?在Rust 编程语境下,Rust 官方之所以仍未提供类似 C++
7. 核心原理:利用
__FUNCTION__ 或 Python __name__ 的内置函数,主要源于其设计哲学、编译模型以及技术实现上的权衡。
以下我们谈谈都有哪些核心原因:
1. 编译期与运行期的严格隔离
Rust 极力追求零成本抽象(Zero-cost Abstractions)。
- 元数据缺失:在编译优化过程中,为了追求极致的执行速度,编译器可能会进行函数内联(Inlining)、符号剥离(Stripping)或单态化(Monomorphization)。这意味着在生成的二进制机器码中,“函数名”这个概念可能已经不存在了。
- 反射受限:Rust 不像 Java 或 C# 那样拥有强大的运行时反射(Runtime Reflection)机制,因为它不想为了存储函数名字符串而让每一个二进制文件都变大。
2. 宏系统的局限性
Rust 有两套宏系统,但都有各自的问题:
- 声明式宏 (
macro_rules!):它在词法分析阶段处理标记(Tokens),它只知道当前位置的符号,并不知道自己“身处哪个函数内部”。 - 过程宏:虽然过程宏可以获取上下文,但它会显著增加编译时间。Rust 团队一直对引入会拖慢编译速度的特性持谨慎态度。
3. 多态与泛型的复杂性
当涉及泛型函数时,函数名变得非常模糊。
- 例如:
print_type::<i32>()和print_type::<String>()。 - 在编译后,它们是两个不同的实体。Rust 应该返回原始模板名,还是带参数的实例名?这种歧义使得官方很难给出一个符合“唯一性”原则的定义。
4. 并非“不提供”,而是“不直接提供”
虽然没有
std::function_name(),但 Rust 提供了构建块:std::any::type_name:这是目前最接近的手段(如你之前看到的function!宏所利用的原理)。它能返回类型的完整路径,包括函数作为“类型”时的路径。std::panic::Location:可以获取文件名、行号和列号。
5. 现状与替代方案
Rust 社区的共识是:如果需要函数名,通常是为了日志(Logging)或追踪(Tracing)。
- Tracing 库:这是目前的行业标准。通过
#[tracing::instrument]属性宏,它可以在编译期自动为函数注入名称和上下文,而无需开发者手动调用获取函数名的函数。 - 待定提案 (RFC):一直有关于
core::intrinsics::current_function_name的讨论,但由于它涉及底层编译器的深度绑定,目前仍未完全稳定。
Rust 不提供直接获取函数名的接口,是为了保证编译后的二进制文件足够小且快,并且避免在泛型和内联场景下产生语义歧义。
一句话总结: Rust 认为“函数名”是属于源代码的属性,而不应该是运行时程序的负担。如果你确实需要它,请通过 宏 或 Tracing 框架 在编译阶段“刻”进代码里。
6. 手搓一个获取函数名函数
废话少说,言归正传。
#[macro_export(local_inner_macros)] macro_rules! function { () => {{ // Okay, this is ugly, I get it. However, this is the best we can get on a stable rust. fn __f() {} fn type_name_of<T>(_: T) -> &'static str { std::any::type_name::<T>() } let name = type_name_of(__f); &name[..name.len() - 5] }}; }
来段非常巧妙且经典的“黑科技”代码,其核心作用是:在不使用不稳定(Unstable)特性或过程宏的前提下,获取当前执行函数的名字。
以下是该代码的详细工作原理解析:
7. 核心原理:利用 std::any::type_name
Rust 的标准库函数
std::any::type_name::<T>() 会返回类型 T 的完整路径字符串。- 内部定义局部函数:在宏内部定义了一个极其简短的局部函数
fn __f() {}。 - 获取函数类型名:通过
type_name_of(__f),Rust 编译器会返回这个局部函数的完整路径。- 通常返回值的格式类似于:
your_crate::module::current_function_name::__f。
- 通常返回值的格式类似于:
- 字符串截断:由于我们只需要当前函数的名字,而不需要末尾的
::__f,代码执行了&name[..name.len() - 5]。- 这里的
5对应的就是::__f这 5 个字符的长度(::__f)。
- 这里的
8. 宏属性解析
#[macro_export]:允许其他 crate 使用这个宏。(local_inner_macros):这是一个关键的优化属性。它确保在该宏内部如果调用了其他同 crate 的宏,即使在外部 crate 被调用时也能正确找到路径,增强了宏的移植性。 强制在宏定义的 Crate 内部找内部宏。
9. 为什么说它“ugly”(丑陋)但必要?
- 现状:至今,Rust 官方尚未在稳定版中提供类似 C++ 的
__FUNCTION__或 Python 的__name__这样的直接宏。 - 替代方案的局限:
- 如果要获取完整、干净的函数名,通常需要依赖 过程宏 (Procedural Macros),但这会显著增加编译时间并引入外部依赖(如
syn,quote)。 - 本方案完全基于 声明式宏 (Declarative Macros),执行速度快,且不依赖任何外部 crate。
- 如果要获取完整、干净的函数名,通常需要依赖 过程宏 (Procedural Macros),但这会显著增加编译时间并引入外部依赖(如
10. 使用示例
fn my_awesome_logic() {
let current_func = function!();
println!("当前正在执行: {}", current_func);
// 输出: my_awesome_logic
}
11. 局限性提示
- 性能开销:虽然比过程宏快,但它涉及字符串的运行时切片操作。在极致性能敏感的循环中频繁调用,会有微小的运行时开销。
- 路径深度:如果你的函数嵌套极深,
type_name返回的字符串可能会非常长。 - 闭包限制:如果在闭包内部调用,它返回的可能是闭包的匿名类型标记,而非你期望的函数名。
总结
这段代码是 Suricata 或类似底层库中常见的“底层适配逻辑”。它通过在函数内部嵌套定义函数并查询其类型名的骚操作,绕过了 Rust 语言层面对反射支持的不足,实现了日志记录和调试中最重要的功能:自动识别当前函数上下文。
参考资料:
浙公网安备 33010602011771号