Solidity 教程系列12 - 库的使用

汇新云】为大家定期更新文章,【汇新云】IT人的产业链平台


Solidity 教程系列第12篇 - Solidity 视图函数、纯函数讲解。

写在前面

Solidity 是以太坊智能合约编程语言,阅读本文前,你应该对以太坊、智能合约有所了解,

如果你还不了解,建议你先看以太坊是什么

库与合约类似,它也部署在一个指定的地址上(仅被部署一次,当代码在不同的合约可反复使用),然后通过EVM的特性DELEGATECALL (Homestead之前是用CALLCODE)来复用代码。库函数在被调用时,库代码是在发起合约(下文称主调合约:主动发起DELEGATECALL调用的合约)的上下文中执行的,使用this将会指向到主调合约,而且库代码可以访问主调合约的存储(storage)。

因为库合约是一个独立的代码,它仅可以访问主调合约明确提供的状态变量,否则,没办法法去知道这些状态变量。

对比普通合约来说,库存在以下的限制(这些限制将来也可能在将来的版本被解除):

无状态变量(state variables)。

不能继承或被继承

不能接收以太币

不能销毁一个库

不会修改状态变量(例如被声明viewpure)库函数只能通过直接调用(如不用DELEGATECALL),是因为其被认为是状态无关的。

库有许多使用场景。两个主要的场景如下:

如果有许多合约,它们有一些共同代码,则可以把共同代码部署成一个库。这将节省gas,因为gas也依赖于合约的规模。因此,可以把库想象成使用其合约的父合约。使用父合约(而非库)切分共同代码不会节省gas,因为在Solidity中,继承通过复制代码工作。

库可用于给数据类型添加成员函数。(参见下一节Using for)

由于库被当作隐式的父合约(不过它们不会显式的出现在继承关系中,但调用库函数和调用父合约的方式是非常类似的,如库L有函数f(),使用L.f()即可访问)。库里面的内部(internal)函数被复制给使用它的合约;

同样按调用内部函数的调用方式,这意味着所有内部类型可以传进去,memory类型则通过引用传递,而不是拷贝的方式。 同样库里面的结构体structs和枚举enums也会被复制给使用它的合约。

因此,如果一个库里只包含内部函数或结构体或枚举,则不需要部署库,因为库里面的所有内容都被复制给使用它的合约。

下面的例子展示了如何使用库。


Solidity 教程系列12 - 库的使用
Solidity 教程系列12 - 库的使用

当然,我们也可以不按上面的方式来使用库函数,可以不定义结构体,可以不使用storage类型引用的参数,还可以在任何位置有多个storage的引用类型的参数。

调用Set.containsSet.removeSet.insert都会编译为以DELEGATECALL的方式调用外部合约和库。如果使用库,需要注意的是一个真实的外部函数调用发生了。尽管msg.sender,msg.value,this还会保持它们在主调合约中的值(在Homestead之前,由于实际使用的是CALLCODE,msg.sender,msg.value会变化)。

下面的例子演示了在库中如何使用memory类型和内部函数(inernal function)来实现一个自定义类型,而不会用到外部函数调用(external function)。


Solidity 教程系列12 - 库的使用


Solidity 教程系列12 - 库的使用

合约的源码中不能添加库地址,它是在编译时向编译器以参数形式提供的(这些地址须由链接器(linker)填进最终的字节码中,使用命令行编译器来进行联接 TODO)。如果地址没有以参数的方式正确给到编译器,编译后的字节码将会仍包含一个这样格式的占们符_Set___(其中Set是库的名称)。可以通过手动将所有的40个符号替换为库的十六进制地址。

Using for 指令

指令using A for B;用来把库函数(从库A)关联到类型B。这些函数将会把调用函数的实例作为第一个参数。语法类似,python中的self变量一样。例如:A库有函数 add(B b1, B b2),则使用Using A for B指令后,如果有B b1就可以使用b1.add(b2)

using A for * 表示库A中的函数可以关联到任意的类型上。

在这两种情形中,所有函数,即使第一个参数的类型与调用函数的对象类型不匹配的,也会被关联上。类型检查是在函数被调用时执行,以及函数重载是也会执行检查。

using A for B; 指令仅在当前的作用域有效,且暂时仅仅支持当前的合约这个作用域,后续也非常有可能解除这个限制,允许作用到全局范围。如果能作用到全局范围,通过引入一些模块(module),数据类型将能通过库函数扩展功能,而不需要每个地方都得写一遍类似的代码了。

下面我们使用Using for 指令方式重写上一节Set的例子:


Solidity 教程系列12 - 库的使用
Solidity 教程系列12 - 库的使用

同样可以使用Using for的方式来对基本类型(elementary types)进行扩展:

Solidity 教程系列12 - 库的使用

需要注意的是所有库调用都实际上是EVM函数调用。这意味着,如果传的是memory类型的,或者是值类型,那么进行一次拷贝,即使是self变量,解决方法是使用存储(storage)类型的引用来避免拷贝内容。


学好区块链,拥抱新未来:

区块链产品经理(点击入驻),和圈内人士混个脸熟