Skip to main content

borsh vec

https://github.com/hpgo6688/anchor/blob/afcbaedac69d23544cad177acfbe10b325cf0a06/ts/packages/borsh/src/index.ts#L195-L210

export function vec<T>(
elementLayout: Layout<T>,
property?: string
): Layout<T[]> {
const length = u32("length");
const layout: Layout<{ values: T[] }> = struct([
length,
seq(elementLayout, offset(length, -length.span), "values"),
]);
return new WrappedLayout(
layout,
({ values }) => values,
(values) => ({ values }),
property
);
}

在这段代码中,{ values } 的使用是为了在 WrappedLayout 中对数据进行解构和重新封装。这涉及到 JavaScript 中对象的结构化(destructuring)和构造(constructing)操作。让我们逐步分析代码的逻辑和 values 的来源以及作用。


代码上下文

vec 函数的作用

vec 函数的目的是为一个动态长度的数组定义二进制数据的布局(Layout)。它使用 buffer-layout 提供的工具(如 seqstruct)来描述数组的二进制结构,并通过 WrappedLayout 包装,使得编码和解码时的数据更加直观。


构造的布局

const layout: Layout<{ values: T[] }> = struct([
length,
seq(elementLayout, offset(length, -length.span), "values"),
]);
  1. length:

    • 定义了一个 u32 类型的字段,表示数组的长度(以字节为单位)。
    • 它的名称是 "length",用来存储数组的元素数量。
  2. seq:

    • 定义了一个序列(数组)的布局,使用 elementLayout 指定数组中每个元素的布局。
    • offset(length, -length.span)
      • 指定数组数据在缓冲区中的起始位置。
      • 它的起始位置是 length 字段的末尾(因为数组数据紧跟在 length 字段之后)。
  3. struct:

    • 定义了一个结构体布局,包含两个字段:
      • length:表示数组的长度。
      • values:表示数组的具体元素。

最终,layout 的结构类似于:

{
length: number; // 数组的长度
values: T[]; // 数组的元素
}

WrappedLayout 包装

return new WrappedLayout(
layout,
({ values }) => values,
(values) => ({ values }),
property
);
  1. WrappedLayout 的作用:

    • WrappedLayout 是一个包装器,它将内部的布局(layout)与用户定义的转换函数结合,使得编码和解码时可以对数据进行自定义的转换。
    • 这里的 layout 是我们定义的结构 { length, values }
  2. WrappedLayout 的参数:

    • 第一个参数:layout,表示内部的实际布局。
    • 第二个参数:解码时的转换函数(fromRaw)。
    • 第三个参数:编码时的转换函数(toRaw)。
    • 第四个参数:可选的属性名称(property)。

为什么是 { values }

关键在于 WrappedLayout 的第二个和第三个参数:

  1. 解码时的转换函数

    ({ values }) => values
    • 解码时,layout.decode() 返回的数据是一个对象 { length, values },其中:
      • length 是数组的长度。
      • values 是数组的具体元素。
    • 由于用户只关心数组的元素(values),所以通过解构 { values } 提取出 values,然后直接返回它。

    举例: 如果 layout.decode() 返回:

    { length: 3, values: [1, 2, 3] }

    那么解构后只会返回:

    [1, 2, 3]
  2. 编码时的转换函数

    (values) => ({ values })
    • 编码时,用户提供的数据是一个数组(例如 [1, 2, 3])。
    • 但是 layout.encode() 需要的数据是一个对象 { length, values },所以需要将数组封装为对象:
      • length 会由 layout 自动计算(根据 values 的长度)。
      • values 是用户提供的数组。
    • 因此,编码时通过 ({ values }) 构造出符合 layout 结构的数据。

    举例: 如果用户提供的数据是:

    [1, 2, 3]

    那么封装后会变成:

    { values: [1, 2, 3] }

总结

  • { values } 是解构和构造对象的关键,用于在 WrappedLayout 中对数据格式进行转换。
  • 解码时:从 { length, values } 中提取 values,让用户只看到数组内容。
  • 编码时:将用户提供的数组封装为 { values } 格式,以便与内部布局匹配。
  • 这种设计的目的是简化用户的使用,让用户与 vec 交互时只需要处理数组(而无需关心长度字段 length)。