多值
原文:https://docs.elrond.com/developers/best-practices/multi-values
## 可变输入输出(多值)
Rust 语言本身不支持变量参数。另一方面,智能合约在接受可变数量的输入或产生可变数量的结果方面没有限制。为了适应这种情况,并使 I/O 处理更简洁,Rust 框架提供了许多所谓的多值类型,它们从多个输入反序列化并序列化为多个输出。
请注意,相同的类型被用作参数和结果。这对于回调之类的地方尤其有意义,异步调用的结果成为回调的输入。
函数支持 4 种类型的变量参数:
OptionalValue<T>
-可以跳过的参数。每个端点可以有多个,但它们都必须是端点的最后一个参数。MultiValueN<T1, T2, ... TN>
-多值元组。可用于返回多个结果(因为 Rust 方法只能有一个结果值)。它们也可以很好地与其他多值结合使用,例如MultiValueEncoded<MultiValue2<usize, BigUint>>
可以接受任意数量的值对,但是如果提供了奇数个参数,就会崩溃。MultiValueEncoded<T>
-可以接收任意数量的参数。注意,每个端点只能使用一个MultiValueEncoded
,并且必须是端点中的最后一个参数。不能在同一个端点中同时使用OptionalValue
和MultiValueEncoded
。它保持其内容被编码,所以当用作参数时它延迟解码,当用作结果时它急切编码。MultiValueManagedVec<T>
-类似于MultiValueEncoded<T>
,但是当用作参数时,它急切地解码,当用作结果时,它迟缓地编码。它实际上是一个具有多值编码的ManagedVec
,所以在这种情况下T
必须是实现ManagedVecItem
的类型。不能包含MultiValueN
等多值。
下面你可以找到一些例子:
#[endpoint(myOptArgEndpoint)]
fn my_opt_arg_endpoint(&self, obligatory_arg: T1, opt_arg: OptionalValue<T2>) {}
#[endpoint(myVarArgsEndpoint)]
fn my_var_args_endpoint(&self, obligatory_arg: T1, args: MultiValueEncoded<T2>) {}
这可能看起来过于复杂,没有什么好的理由。为什么不干脆用Option<T>
代替OptionalValue<T>
,用ManagedVec<T>
代替MultiValueEncoded<T>
?原因是它们各自使用的编码类型。
选项< T > vs 选项值< T >
让我们以下列端点为例:
#[endpoint(myOptArgEndpoint)]
fn my_opt_arg_endpoint(&self, token_id: TokenIdentifier, opt_nonce: Option<u64>) {}
#[endpoint(myOptArgEndpoint)]
fn my_opt_arg_endpoint(&self, token_id: TokenIdentifier, opt_nonce: OptionalValue<u64>) {}
具有以下参数:TOKEN-123456(0x 544 F4 b 454 e2d 313233343536)和 5。
最重要的因素是用户体验,但也有效率的问题。对于第一个,调用数据看起来像这样(注意Some(T)
前面的01
)myOptArgEndpoint@544f4b454e2d313233343536@010000000000000005
而对于第二个,这是一个非常干净:myOptArgEndpoint@544f4b454e2d313233343536@05
对于相同的代币 ID 和跳过的 nonce,编码如下:myOptArgEndpoint@544f4b454e2d313233343536@00
myOptArgEndpoint@544f4b454e2d313233343536
如你所见,参数可以被完全跳过,而不是传递一个00
( None
)。
managed vecvs 多值编码< T >
出于示例的目的,我们假设您想要接收成对的(代币 ID、nonce、amount)。这可以通过两种方式实现:
#[endpoint(myVarArgsEndpoint)]
fn my_var_args_endpoint(&self, args: ManagedVec<(TokenIdentifier, u64, BigUint)>) {}
#[endpoint(myVarArgsEndpoint)]
fn my_var_args_endpoint(&self, args: MultiValueManagedVec<TokenIdentifier, u64, BigUint>) {}
第一种方法看起来简单得多,只是元组的ManagedVec
。但是,这对性能和可用性的影响是毁灭性的。要使用具有对(TOKEN-123456,5,100)和对(TOKEN-123456,10,500)的第一个端点,呼叫数据必须如下所示:myVarArgsEndpoint@0000000c_544f4b454e2d313233343536_0000000000000005_00000001_64_0000000c_544f4b454e2d313233343536_000000000000000a_00000002_01f4
注意:在上面,我们用_
分隔各部分只是为了可读性。在真正的区块链上,没有下划线,所有的东西都是连在一起的。
如您所见,这个端点很难使用。所有参数都必须通过嵌套编码传递到这个大块中,这也增加了TokenIdentifier
(即前面的 0000000c,长度为 12)和BigUint
(即以字节为单位的长度)的额外长度。
对于MultiValueEncoded
方法,这个端点更容易使用。对于相同的参数,调用数据如下所示:myVarArgsEndpoint@544f4b454e2d313233343536@05@64@544f4b454e2d313233343536@0a@01f4
调用数据要短得多,可读性也强得多,因为我们使用顶层编码而不是嵌套编码,所以也不需要长度。