# 庫(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智能合約文件結構
- 智能合約源文件的基本要素概覽
- 值類型
- 類型
- 布爾
- 整型
- 地址
- 字節數組
- 小數
- 字符串
- 十六進制字面量
- 枚舉
- 函數
- 引用類型
- 引用類型
- 數據位置
- 數組
- 數據結構
- 雜項
- 映射
- 左值運算符
- 類型間的轉換
- 類型推斷
- 單位
- 貨幣單位
- 時間單位
- 語言內置特性
- 特殊變量及函數
- 數學和加密函數
- 地址相關
- 進階
- 入參和出參
- 控制結構
- 函數調用
- 創建合約實例
- 表達式的執行順序
- 賦值
- 作用范圍和聲明
- 異常
- 內聯匯編
- 合約詳解
- 合約
- 可見性或權限控制
- 訪問函數
- 函數修改器
- 常狀態變量
- 回退函數
- 事件
- 繼承
- 接口
- 其它
- 庫
- 狀態變量的存儲模型
- 內存變量的存局
- 調用數據的布局