你可能被告知Rust是复杂的,但这不是真的,实际上Rust比你想象的要复杂得多!在这篇文章中,将向你展示Rust隐藏其复杂性的不同方式。
首先,让我们从一个看似简单的hello world程序开始。
图片
程序看起来很简单,只是一个主函数和一个打印语句。但这不是编译器看到的,注意println!不是一个函数,它是一个宏,这意味着它在编译时扩展成更复杂的代码。
在这个例子中,还有一些不是由宏生成的隐藏代码,这个隐藏代码我们可以使用一个叫做cargo expand的工具:
图片
cargo expand是一个强大的开发工具,它向我们展示了编译器在展开宏和应用编译器转换后看到的完整rust代码。
如果我们运行cargo expand,hello world示例将展开为以下代码:
图片
让我们一步一步来分析代码,前三行导入Prelude,它是一组在标准库中定义的常用项,包括常用的Trait、Box智能指针、Option和Result枚举等。
第一行启用了prelude_import特性,通常表示不稳定或不断发展的功能。prelude_import特性允许Rust团队可以在编译器内部修改处理prelude_import的方式,从而在不影响用户代码的情况下实现潜在的优化。
第二行是一个特殊属性,确保Prelude中的各项在你的整个代码库中可用。
第三行是将Rust 2021版本中的所有项导入到当前作用域内。每3年升级一次的这种Rust版本是一种引入突破性变化的机制,而不是强迫每个人每3年必须升级一次。新的版本会引入新的功能和语法,但这些都是可选择的,这样你就不必升级每个crate,可以选择对你有用的新特性,且一个版本中的crate必须与其他版本中编译的crate无缝互操作。这意味着你可以获得新特性的好处,而不会破坏现有的代码。
接下来的两行告诉编译器使用Rust标准库宏。use是一个属性,表示允许使用标准库中的宏,而不需要显式导入。extern crate std声明了对标准库的依赖,大多数crate不需要extern,因为编译器会自动链接Cargo.toml中定义的依赖项。这里使用extern crate std行来支持旧版本的println!宏。
让我们看一下main中的代码,这是println!宏展开成的代码,_print是rust的IO模块中的底层函数,处理实际的字符串打印。
format_args!是另一个宏,可以处理任何格式化参数的字符串格式化。它是一个特殊的宏,直接内置于编译器中,实际上不会扩展为rust代码。
让我们看一下_print的定义:
图片
我们看到它调用了一个更底层的函数print_to,它接受格式化的字符串参数,一个缓冲区,在这个例子中是标准输出,一个标签,然后将字符串打印到缓冲区并处理任何可能的错误。
回到我们的hello world示例,我们看到_print在内部作用域中被调用,这是Rust处理宏展开的一部分。内部作用域中确保宏展开期间创建的临时变量或表达式不会泄漏到周围的作用域中,并有助于防止意外的变量遮蔽或生命周期问题。
现在,为了运行这个扩展代码,需要切换到nightly编译器,并启用print internal特性:
图片
因为这段代码使用了内部编译器特性,而这些特性不是rust稳定的公共API的一部分。现在我们可以运行我们的hello world示例,正如你所看到的,rust在幕后做了很多工作,即使是简单的程序,比如println!宏,也被扩展成了更复杂的优化代码。
总结
Rust会自动导入常用的项并链接到标准库,甚至简单的操作都有我们看不到的安全检查和优化,Rust管理复杂性的方法远远超出了我们的hello world示例。
从使用所有权系统自动管理内存,到使用Result类型和问号操作符简化错误处理,再到提供包管理器、检查器和格式化器等内置工具。Rust一直致力于管理复杂性,让我们的编程更轻松。