本文详细介绍了Solana生态系统中Borsh序列化机制的工作原理。文章解释了序列化与反序列化的概念,Borsh作为Solana标准序列化格式的特点(确定性、紧凑性),以及如何处理固定长度和可变长度数据类型。同时,通过代码示例展示了如何在Solana Native程序中序列化和反序列化数据,并提及了手动读取数据和访问账户元数据的方法。
在之前的教程中,我们学习了如何读取传递给程序的账户。我们看到调用 account.try_borrow_data() 会将账户的 data 字段作为一个原始字节切片(例如 [0x01, 0x00, 0x00, 0x00])的引用。
Solana 将所有账户数据存储为字节。为了使用像 Rust 结构体这样的高级数据结构,我们使用序列化将结构体转换为字节,以便在链上存储,并在读取时将这些字节反序列化回结构体。Solana 使用 Borsh 作为其标准序列化格式。
本文解释了 Borsh 序列化如何工作以及如何解释这些原始字节。
在本教程中,我们将展示以下内容:
序列化是将数据结构(如 Rust 结构体或字符串)转换为可以存储或传输的字节序列的过程。反序列化是相反的过程——将这些字节转换回原始数据结构。
例如,如果你有一个计数器值为 42(存储为 u64)的结构体,序列化会将其转换为 8 个字节:[0x2A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]。第一个字节 0x2A 以小端序存储值的最低有效字节,其余 7 个字节为零,因为 u64 在内存中存储为 8 个字节。稍后,当你需要读取这些数据时,反序列化会将这些字节转换回你的结构体。
Borsh(用于哈希的二进制对象表示序列化器)是一种序列化格式,它定义了将 Rust 结构体转换为字节并返回的规则。Solana 使用 Borsh 作为其标准序列化格式,因为它具有以下特点:
borsh Rust crate 实现了 Solana 程序使用的序列化格式。
Solana 程序使用 Borsh 序列化指令数据和链上账户数据。对于账户,Borsh 将 Rust 结构体及其字段(字符串、整数、布尔值、向量等)转换为存储在账户的 data 字段中的原始字节。
在原生 Rust 程序中,我们定义结构体来表示我们想要存储的账户数据(类似于你在 Anchor 中定义账户结构体的方式)。然后 Borsh 将这些结构体序列化为填充账户 data 字段的字节。序列化时,结构体字段在账户的 data 字段中按声明顺序连续排列。
基本流程如下图所示:

此图表显示:
CounterData { count: 42 })使用 Borsh 转换为原始字节data 字段中在 Anchor 中,序列化被抽象化了,但在原生 Rust 程序中,我们为结构体添加 #[derive(BorshSerialize, BorshDeserialize)] 属性。这会告诉 Borsh 库在编译时自动生成序列化和反序列化代码。
例如,这是一个 CounterData 结构体,它将计数器值存储为 u64:
#[derive(BorshSerialize, BorshDeserialize)]
pub struct CounterData {
pub count: u64,
}
如果 count 值为 42,BorshSerialize 属性会将此结构体序列化为:

具体过程如下:
0x2Acount 被定义为 u64,Borsh 使用 8 个字节以小端序格式表示它0x2A 之后的 7 个字节为零,因为 u64 占用 8 个字节在查看更复杂的示例之前,让我们了解 Borsh 如何处理没有固定长度的数据类型,例如字符串和向量。
与 u64、bool 和 u8 等固定数据类型不同,它们无论值如何都使用相同数量的字节,String 和 Vec<T> 等可变长度数据类型的大小取决于其内容。
对于可变长度类型,Borsh 使用长度前缀:它首先写入数据的大小,然后是实际数据。这告诉 Borsh 在反序列化时需要读取多少字节(没有它,Borsh 将不知道一个字段在哪里结束,另一个在哪里开始)。
其工作原理如下:
u32(4 字节)的小端序格式序列化例如,如果我们有一个字符串“hi”:
u32 的小端序序列化:[0x02, 0x00, 0x00, 0x00] (2 字节)。由于长度存储为 u32,字符串的最大理论大小为 2^32 - 1 字节[0x68, 0x69]这给我们带来了最终结果:

这个长度前缀有助于 Borsh 在反序列化时准确地知道可变长度类型需要读取多少字节。
假设我们有一个 UserData 结构体,包含多个字段来表示存储账户的用户信息:
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!