# 库(Libraries)
库与合约类似,但它的目的是在一个指定的地址,且仅部署一次,然后通过EVM的特性`DELEGATECALL`(Homestead之前是用`CALLCODE`)来复用代码。这意味着库函数调用时,它的代码是在调用合约的上下文中执行。使用`this`将会指向到调用合约,而且可以访问调用合约的`存储(storage)`。因为一个合约是一个独立的代码块,它仅可以访问调用合约明确提供的`状态变量(state variables)`,否则除此之外,没有任何方法去知道这些状态变量。
使用库合约的合约,可以将库合约视为隐式的`父合约(base contracts)`,当然它们不会显式的出现在继承关系中。但调用库函数的方式非常类似,如库`L`有函数`f()`,使用`L.f()`即可访问。此外,`internal`的库函数对所有合约可见,如果把库想像成一个父合约就能说得通了。当然调用内部函数使用的是`internal`的调用惯例,这意味着所有`internal`类型可以传进去,`memory`类型则通过引用传递,而不是拷贝的方式。为了在EVM中实现这一点,`internal`的库函数的代码和从其中调用的所有函数将被`拉取(pull into)`到调用合约中,然后执行一个普通的`JUMP`来代替`DELEGATECALL`。
下面的例子展示了如何使用库(后续在`using for`章节有一个更适合的实现`Set`的例子)。
```
pragma solidity ^0.4.0;
library Set {
// We define a new struct datatype that will be used to
// hold its data in the calling contract.
struct Data { mapping(uint => bool) flags; }
// Note that the first parameter is of type "storage
// reference" and thus only its storage address and not
// its contents is passed as part of the call. This is a
// special feature of library functions. It is idiomatic
// to call the first parameter 'self', if the function can
// be seen as a method of that object.
function insert(Data storage self, uint value)
returns (bool)
{
if (self.flags[value])
return false; // already there
self.flags[value] = true;
return true;
}
function remove(Data storage self, uint value)
returns (bool)
{
if (!self.flags[value])
return false; // not there
self.flags[value] = false;
return true;
}
function contains(Data storage self, uint value)
returns (bool)
{
return self.flags[value];
}
}
contract C {
Set.Data knownValues;
function register(uint value) {
// The library functions can be called without a
// specific instance of the library, since the
// "instance" will be the current contract.
if (!Set.insert(knownValues, value))
throw;
}
// In this contract, we can also directly access knownValues.flags, if we want.
}
```
上面的例子中:
- `Library`定义了一个数据结构体,用来在调用的合约中使用(库本身并未实际存储的数据)。如果函数需要操作数据,这个数据一般是通过库函数的第一个参数传入,按惯例会把参数名定为`self`。
- 另外一个需要留意的是上例中`self`的类型是`storage`,那么意味着传入的会是一个引用,而不是拷贝的值,那么修改它的值,会同步影响到其它地方,俗称引用传递,非值传递。
- 库函数的使用不需要实例化,`c.register`中可以看出是直接使用`Set.insert`。但实际上当前的这个合约本身就是它的一个实例。
- 这个例子中,`c`可以直接访问,`knownValues`。虽然这个值主要是被库函数使用的。
当然,你完全可以不按上面的方式来使用库函数,可以不需要定义结构体,不需要使用`storage`类型的参数,还可以在任何位置有多个`storage`的引用类型的参数。
调用`Set.contains`,`Set.remove`,`Set.insert`都会编译为以`DELEGATECALL`的方式调用`external`的合约和库。如果使用库,需要注意的是一个实实在在的外部函数调用发生了。尽管`msg.sender`,`msg.value`,`this`还会保持它们在此调用中的值(在`Homestead`之前,由于实际使用的是`CALLCODE`,`msg.sender`,`msg.value`会变化)。
下面的例子演示了如何使用`memory`类型和`内部函数(inernal function)`,来实现一个自定义类型,但不会用到`外部函数调用(external function)`。
```
pragma solidity ^0.4.0;
library BigInt {
struct bigint {
uint[] limbs;
}
function fromUint(uint x) internal returns (bigint r) {
r.limbs = new uint[](1);
r.limbs[0] = x;
}
function add(bigint _a, bigint _b) internal returns (bigint r) {
r.limbs = new uint[](max(_a.limbs.length, _b.limbs.length));
uint carry = 0;
for (uint i = 0; i < r.limbs.length; ++i) {
uint a = limb(_a, i);
uint b = limb(_b, i);
r.limbs[i] = a + b + carry;
if (a + b < a || (a + b == uint(-1) && carry > 0))
carry = 1;
else
carry = 0;
}
if (carry > 0) {
// too bad, we have to add a limb
uint[] memory newLimbs = new uint[](r.limbs.length + 1);
for (i = 0; i < r.limbs.length; ++i)
newLimbs[i] = r.limbs[i];
newLimbs[i] = carry;
r.limbs = newLimbs;
}
}
function limb(bigint _a, uint _limb) internal returns (uint) {
return _limb < _a.limbs.length ? _a.limbs[_limb] : 0;
}
function max(uint a, uint b) private returns (uint) {
return a > b ? a : b;
}
}
contract C {
using BigInt for BigInt.bigint;
function f() {
var x = BigInt.fromUint(7);
var y = BigInt.fromUint(uint(-1));
var z = x.add(y);
}
}
```
因为编译器并不知道库最终部署的地址。这些地址须由`linker`填进最终的字节码中(使用[命令行编译器](http://solidity.readthedocs.io/en/develop/miscellaneous.html#commandline-compiler)来进行联接)。如果地址没有以参数的方式正确给到编译器,编译后的字节码将会仍包含一个这样格式的占们符`_Set___`(其中`Set`是库的名称)。可以通过手动将所有的40个符号替换为库的十六进制地址。
对比普通合约来说,库的限制:
- 无`状态变量(state variables)`。
- 不能继承或被继承
- 不能接收`ether`。
这些限制将来也可能被解除!
## 附着库(Using for)
指令`using A for B;`用来附着库里定义的函数(从库`A`)到任意类型`B`。这些函数将会默认接收调用函数对象的实例作为第一个参数。语法类似,`python`中的`self`变量一样。
`using A for *`的效果是,库`A`中的函数被附着在做任意的类型上。
在这两种情形中,所有函数,即使那些第一个参数的类型与调用函数的对象类型不匹配的,也被附着上了。类型检查是在函数被真正调用时,函数重载检查也会执行。
`using A for B;`指令仅在当前的作用域有效,且暂时仅仅支持当前的合约这个作用域,后续也非常有可能解除这个限制,允许作用到全局范围。如果能作用到全局范围,通过引入一些模块(module),数据类型将能通过库函数扩展功能,而不需要每个地方都得写一遍类似的代码了。
下面我们来换个方式重写`set`的例子。
```
pragma solidity ^0.4.0;
// This is the same code as before, just without comments
library Set {
struct Data { mapping(uint => bool) flags; }
function insert(Data storage self, uint value)
returns (bool)
{
if (self.flags[value])
return false; // already there
self.flags[value] = true;
return true;
}
function remove(Data storage self, uint value)
returns (bool)
{
if (!self.flags[value])
return false; // not there
self.flags[value] = false;
return true;
}
function contains(Data storage self, uint value)
returns (bool)
{
return self.flags[value];
}
}
contract C {
using Set for Set.Data; // this is the crucial change
Set.Data knownValues;
function register(uint value) {
// Here, all variables of type Set.Data have
// corresponding member functions.
// The following function call is identical to
// Set.insert(knownValues, value)
if (!knownValues.insert(value))
throw;
}
}
```
我们也可以通过这种方式来扩展`基本类型(elementary types)`。
```
pragma solidity ^0.4.0;
library Search {
function indexOf(uint[] storage self, uint value) returns (uint) {
for (uint i = 0; i < self.length; i++)
if (self[i] == value) return i;
return uint(-1);
}
}
contract C {
using Search for uint[];
uint[] data;
function append(uint value) {
data.push(value);
}
function replace(uint _old, uint _new) {
// This performs the library function call
uint index = data.indexOf(_old);
if (index == uint(-1))
data.push(_new);
else
data[index] = _new;
}
}
```
需要注意的是所有库调用都实际上是EVM函数调用。这意味着,如果你传的是`memory`类型的,或者是`值类型(vaue types)`,那么仅会传一份拷贝,即使是`self`变量。变通之法就是使用`存储(storage)`类型的变量,这样就不会拷贝内容。
- Solidity语言
- 入门说明
- Solidity智能合约文件结构
- 智能合约源文件的基本要素概览
- 值类型
- 类型
- 布尔
- 整型
- 地址
- 字节数组
- 小数
- 字符串
- 十六进制字面量
- 枚举
- 函数
- 引用类型
- 引用类型
- 数据位置
- 数组
- 数据结构
- 杂项
- 映射
- 左值运算符
- 类型间的转换
- 类型推断
- 单位
- 货币单位
- 时间单位
- 语言内置特性
- 特殊变量及函数
- 数学和加密函数
- 地址相关
- 进阶
- 入参和出参
- 控制结构
- 函数调用
- 创建合约实例
- 表达式的执行顺序
- 赋值
- 作用范围和声明
- 异常
- 内联汇编
- 合约详解
- 合约
- 可见性或权限控制
- 访问函数
- 函数修改器
- 常状态变量
- 回退函数
- 事件
- 继承
- 接口
- 其它
- 库
- 状态变量的存储模型
- 内存变量的存局
- 调用数据的布局