跳到主要内容

给Substrate开发者的Rust说明书

ref: https://docs.substrate.io/learn/rust-basics/

Substrate之所以能成为一个灵活和可扩展的框架,用于创建关键任务软件,很大程度上要归功于Rust。作为Substrate的首选语言,Rust是一种高性能的编程语言,有以下几个方面的考量:

  • Rust是快速的:它在编译时是静态类型的,使得编译器可以优化代码的速度,开发者也可以针对特定的编译目标进行优化。
  • Rust是可移植的:它设计为可以运行在嵌入式设备上,并支持任何类型的操作系统。
  • Rust是内存安全的:它没有垃圾回收器,并且检查你使用的每一个变量和每一个内存地址,以避免任何内存泄漏。
  • Rust是Wasm优先的:它有一流的工具支持编译到WebAssembly。

Substrate中的Rust

架构部分,你将了解到Substrate由两个不同的架构层面的组件组成:外层节点和Runtime。虽然Rust的一些更复杂的特性,如多线程和异步Rust,被用于外层节点代码中,但它们并没有直接暴露给Runtime工程师,这使得Runtime工程师更容易专注于他们的节点的业务逻辑。

一般来说,根据他们的关注点,开发者应该了解:

在深入Substrate之前,有必要熟悉一下Rust——有许多资源可以学习Rust,包括Rust语言编程书Rust示例。本节的其余部分将重点讲解一下在Substrate中使用Rust的一些核心特性的方式,以帮助刚开始学习Runtime开发的开发者。

编译目标

当构建一个Substrate节点时,我们使用wasm32-unknown-unknown编译目标,这意味着Substrate Runtime工程师只能编写能够编译成Wasm的Runtime。这意味着你不能依赖一些典型的标准库类型和函数,而必须只使用no_std兼容的crate来编写大部分的Runtime代码。Substrate有很多自己的原始类型和相关的trait,使得它可以绕过no_std的要求,以使得编码更加容易。

当你学习如何使用和编写FRAME pallets时,你会发现有许多宏对可重用的代码进行了抽象,用于常见的任务或强制执行Runtime特定的要求。这些宏让你可以专注于编写符合Rust惯用法和特定应用的逻辑代码,而不是与Runtime交互所需的通用样板代码。

Rust宏是一个强大的工具,可以帮助确保满足某些要求(而不需要重写代码),例如逻辑要以特定的方式格式化,进行特定的检查,或者一些逻辑由特定的数据结构组成。这对于帮助开发者编写可以与Substrate Runtime 集成的代码(通常比较复杂)特别有用。例如,#[frame_system::pallet]宏是所有FRAME pallets所必需的,它可以帮助你正确地实现一些必需的属性——例如存储项或外部可调用函数,并使其与construct_runtime中的构建过程兼容。

开发Substrate Runtime涉及到大量使用Rust的属性宏,它们有两种风格:派生属性和自定义属性。当你开始使用Substrate时,知道它们如何工作并不是那么重要,而是要知道它们的存在,能够让你编写正确的Runtime代码。

派生属性对于需要满足某些trait的自定义Runtime类型很有用,例如,让类型在Runtime执行期间可以被节点解码。

其他属性宏也在Substrate的代码库中很常见,用于:

  • 指定一个代码片段只编译到no_std,或者可以使用std库。
  • 构建自定义的FRAME pallets。
  • 指定Runtime的构建方式。

泛型和配置trait

Rust中的trait类似于Java等语言中的接口,它们提供了一种给类型赋予高级功能的方法。

如果你已经了解了pallets,你可能已经注意到每个pallet都有一个Config trait,它允许你定义pallet所依赖的类型和接口。

这个trait本身继承了一些来自frame_system::pallet::Config trait 的核心Runtime类型,这使得在编写Runtime逻辑时更容易访问常见的类型。此外,在任何FRAME pallet中,Config trait拥有一个泛型,泛型参数为T(关于泛型的更多内容在下一节)。在这些核心Runtime类型中,常见的有 T::AccountId,用于在Runtime中区分常用的用户类型,T::BlockNumber,Runtime中使用的区块编号类型。

关于Rust中的泛型和trait的更多信息,请参阅Rust Book中的泛型trait高级trait部分。

使用Rust泛型,Substrate Runtime开发者可以编写完全不依赖于特定实现细节的pallets,从而充分利用Substrate的灵活性、可扩展性和模块化能力。

Config trait中的所有类型都定义成被trait约束的泛型,并在Runtime实现中具体化。这不仅意味着你可以编写支持同一类型的不同规范的pallets(例如,Substrate和Ethereum链的地址),而且你还可以根据自己的需要以最小的开销定制泛型实现(例如,将区块编号改为u32)。

这给开发者提供了编写代码的灵活性,使代码不需要对你所做的核心区块链架构决策做出任何假设。

Substrate最大限度地利用泛型来提供最大的灵活性。由你来定义泛型如何具体化以适应你的目的。

关于Rust中的泛型和trait的更多信息,请参阅Rust Book中的泛型部分。

下一步

现在你已经知道Substrate是如何依赖于几个关键的Rust特性——如trait、泛型和宏,接下来你可以探索以下资源来学习更多知识。