# 2 – 基本概念
本章描述了語言的基本概念。
## 2.1 – 值與類型
Lua 是一門_動態類型語言_。 這意味著變量沒有類型;只有值才有類型。 語言中不設類型定義。 所有的值攜帶自己的類型。
Lua 中所有的值都是 _一等公民_。 這意味著所有的值均可保存在變量中、 當作參數傳遞給其它函數、以及作為返回值。
Lua 中有八種基本類型: _nil_、_boolean_、_number_、_string_、_function_、_userdata_、 _thread_ 和 _table_。 _Nil_ 是值 **nil** 的類型, 其主要特征就是和其它值區別開;通常用來表示一個有意義的值不存在時的狀態。 _Boolean_ 是 **false** 與 **true** 兩個值的類型。 **nil** 和 **false** 都會導致條件判斷為假; 而其它任何值都表示為真。 _Number_ 代表了整數和實數(浮點數)。 _String_ 表示一個不可變的字節序列。 Lua 對 8 位是友好的: 字符串可以容納任意 8 位值, 其中包含零 ('`\0`') 。 Lua 的字符串與編碼無關; 它不關心字符串中具體內容。
_number_ 類型有兩種內部表現方式, _整數_ 和 _浮點數_。 對于何時使用哪種內部形式,Lua 有明確的規則, 但它也按需(參見 [§3.4.3](#3.4.3))作自動轉換。 因此,程序員多數情況下可以選擇忽略整數與浮點數之間的差異或者假設完全控制每個數字的內部表現方式。 標準 Lua 使用 64 位整數和雙精度(64 位)浮點數, 但你也可以把 Lua 編譯成使用 32 位整數和單精度(32 位)浮點數。 以 32 位表示數字對小型機器以及嵌入式系統特別合適。 (參見 `luaconf.h` 文件中的宏 `LUA_32BITS` 。)
Lua 可以調用(以及操作)用 Lua 或 C (參見 [§3.4.10](#3.4.10))編寫的函數。 這兩種函數有統一類型 _function_。
_userdata_ 類型允許將 C 中的數據保存在 Lua 變量中。 用戶數據類型的值是一個內存塊, 有兩種用戶數據: _完全用戶數據_ ,指一塊由 Lua 管理的內存對應的對象; _輕量用戶數據_ ,則指一個簡單的 C 指針。 用戶數據在 Lua 中除了賦值與相等性判斷之外沒有其他預定義的操作。 通過使用 _元表_ ,程序員可以給完全用戶數據定義一系列的操作 (參見 [§2.4](#2.4))。 你只能通過 C API 而無法在 Lua 代碼中創建或者修改用戶數據的值, 這保證了數據僅被宿主程序所控制。
_thread_ 類型表示了一個獨立的執行序列,被用于實現協程 (參見 [§2.6](#2.6))。 Lua 的線程與操作系統的線程毫無關系。 Lua 為所有的系統,包括那些不支持原生線程的系統,提供了協程支持。
_table_ 是一個關聯數組, 也就是說,這個數組不僅僅以數字做索引,除了 **nil** 和 NaN 之外的所有 Lua 值 都可以做索引。 (_Not a Number_ 是一個特殊的數字,它用于表示未定義或表示不了的運算結果,比如 `0/0`。) 表可以是 _異構_ 的; 也就是說,表內可以包含任何類型的值( **nil** 除外)。 任何鍵的值若為 **nil** 就不會被記入表結構內部。 換言之,對于表內不存在的鍵,都對應著值 **nil** 。
表是 Lua 中唯一的數據結構, 它可被用于表示普通數組、序列、符號表、集合、記錄、圖、樹等等。 對于記錄,Lua 使用域名作為索引。 語言提供了 `a.name` 這樣的語法糖來替代 `a["name"]` 這種寫法以方便記錄這種結構的使用。 在 Lua 中有多種便利的方式創建表(參見 [§3.4.9](#3.4.9))。
我們使用 _序列_ 這個術語來表示一個用 {1.._n_} 的正整數集做索引的表。 這里的非負整數 _n_ 被稱為該序列的長度(參見 [§3.4.7](#3.4.7))。
和索引一樣,表中每個域的值也可以是任何類型。 需要特別指出的是:既然函數是一等公民,那么表的域也可以是函數。 這樣,表就可以攜帶 _方法_ 了。 (參見 [§3.4.11](#3.4.11))。
索引一張表的原則遵循語言中的直接比較規則。 當且僅當 `i` 與 `j`直接比較相等時 (即不通過元方法的比較), 表達式 `a[i]` 與 `a[j]` 表示了表中相同的元素。 特別指出:一個可以完全表示為整數的浮點數和對應的整數相等 (例如:`1.0 == 1`)。 為了消除歧義,當一個可以完全表示為整數的浮點數做為鍵值時, 都會被轉換為對應的整數儲存。 例如,當你寫 `a[2.0] = true` 時, 實際被插入表中的鍵是整數 `2` 。 (另一方面,2 與 "`2`" 是兩個不同的 Lua 值, 故而它們可以是同一張表中的不同項。)
表、函數、線程、以及完全用戶數據在 Lua 中被稱為 _對象_: 變量并不真的 _持有_ 它們的值,而僅保存了對這些對象的 _引用_。 賦值、參數傳遞、函數返回,都是針對引用而不是針對值的操作, 這些操作均不會做任何形式的隱式拷貝。
庫函數 [`type`](#pdf-type) 用于以字符串形式返回給定值的類型。 (參見 [§6.1](#6.1))。
## 2.2 – 環境與全局環境
后面在 [§3.2](#3.2) 以及 [§3.3.3](#3.3.3) 會討論, 引用一個叫 `var` 的自由名字(指在任何層級都未被聲明的名字) 在句法上都被翻譯為 `_ENV.var` 。 此外,每個被編譯的 Lua 代碼塊都會有一個外部的局部變量叫 `_ENV` (參見 [§3.3.2](#3.3.2)), 因此,`_ENV` 這個名字永遠都不會成為一個代碼塊中的自由名字。
在轉譯那些自由名字時,`_ENV` 是否是那個外部的局部變量無所謂。 `_ENV` 和其它你可以使用的變量名沒有區別。 這里特別指出,你可以定義一個新變量或指定一個參數叫這個名字。 當編譯器在轉譯自由名字時所用到的 `_ENV` , 指的是你的程序在那個點上可見的那個名為 _ENV 的變量。 (Lua 的可見性規則參見 [§3.5](#3.5))
被 `_ENV` 用于值的那張表被稱為 _環境_。
Lua 保有一個被稱為 _全局環境_ 特別環境。它被保存在 C 注冊表 (參見 [§4.5](#4.5))的一個特別索引下。 在 Lua 中,全局變量 [`_G`](#pdf-_G) 被初始化為這個值。 ([`_G`](#pdf-_G) 不被內部任何地方使用。)
當 Lua 加載一個代碼塊,`_ENV` 這個上值的默認值就是這個全局環境 (參見 [`load`](#pdf-load))。 因此,在默認情況下,Lua 代碼中提及的自由名字都指的全局環境中的相關項 (因此,它們也被稱為 _全局變量_ )。 此外,所有的標準庫都被加載入全局環境,一些函數也針對這個環境做操作。 你可以用 [`load`](#pdf-load) (或 [`loadfile`](#pdf-loadfile))加載代碼塊,并賦予它們不同的環境。 (在 C 里,當你加載一個代碼塊后,可以通過改變它的第一個上值來改變它的環境。)
## 2.3 – 錯誤處理
由于 Lua 是一門嵌入式擴展語言,其所有行為均源于宿主程序中 C 代碼對某個 Lua 庫函數的調用。 (單獨使用 Lua 時,`lua` 程序就是宿主程序。) 所以,在編譯或運行 Lua 代碼塊的過程中,無論何時發生錯誤, 控制權都返回給宿主,由宿主負責采取恰當的措施(比如打印錯誤消息)。
可以在 Lua 代碼中調用 [`error`](#pdf-error) 函數來顯式地拋出一個錯誤。 如果你需要在 Lua 中捕獲這些錯誤, 可以使用 [`pcall`](#pdf-pcall) 或 [`xpcall`](#pdf-xpcall) 在 _保護模式_ 下調用一個函數。
無論何時出現錯誤,都會拋出一個攜帶錯誤信息的 _錯誤對象_ (_錯誤消息_)。 Lua 本身只會為錯誤生成字符串類型的錯誤對象, 但你的程序可以為錯誤生成任何類型的錯誤對象, 這就看你的 Lua 程序或宿主程序如何處理這些錯誤對象。
使用 [`xpcall`](#pdf-xpcall) 或 [`lua_pcall`](#lua_pcall) 時, 你應該提供一個 _消息處理函數_ 用于錯誤拋出時調用。 該函數需接收原始的錯誤消息,并返回一個新的錯誤消息。 它在錯誤發生后棧尚未展開時調用, 因此可以利用棧來收集更多的信息, 比如通過探知棧來創建一組棧回溯信息。 同時,該處理函數也處于保護模式下,所以該函數內發生的錯誤會再次觸發它(遞歸)。 如果遞歸太深,Lua 會終止調用并返回一個合適的消息。
## 2.4 – 元表及元方法
Lua 中的每個值都可以有一個 _元表_。 這個 _元表_ 就是一個普通的 Lua 表, 它用于定義原始值在特定操作下的行為。 如果你想改變一個值在特定操作下的行為,你可以在它的元表中設置對應域。 例如,當你對非數字值做加操作時, Lua 會檢查該值的元表中的 "`__add`" 域下的函數。 如果能找到,Lua 則調用這個函數來完成加這個操作。
元表中的鍵對應著不同的 _事件_ 名; 鍵關聯的那些值被稱為 _元方法_。 在上面那個例子中引用的事件為 `"add"` , 完成加操作的那個函數就是元方法。
你可以用 [`getmetatable`](#pdf-getmetatable) 函數 來獲取任何值的元表。
使用 [`setmetatable`](#pdf-setmetatable) 來替換一張表的元表。在 Lua 中,你不可以改變表以外其它類型的值的元表 (除非你使用調試庫(參見[§6.10](#6.10))); 若想改變這些非表類型的值的元表,請使用 C API。
表和完全用戶數據有獨立的元表 (當然,多個表和用戶數據可以共享同一個元表)。 其它類型的值按類型共享元表; 也就是說所有的數字都共享同一個元表, 所有的字符串共享另一個元表等等。 默認情況下,值是沒有元表的, 但字符串庫在初始化的時候為字符串類型設置了元表 (參見 [§6.4](#6.4))。
元表決定了一個對象在數學運算、位運算、比較、連接、 取長度、調用、索引時的行為。 元表還可以定義一個函數,當表對象或用戶數據對象在垃圾回收 (參見[§2.5](#2.5))時調用它。
接下來會給出一張元表可以控制的事件的完整列表。 每個操作都用對應的事件名來區分。 每個事件的鍵名用加有 '`__`' 前綴的字符串來表示; 例如 "add" 操作的鍵名為字符串 "`__add`"。 注意、Lua 從元表中直接獲取元方法; 訪問元表中的元方法永遠不會觸發另一次元方法。 下面的代碼模擬了 Lua 從一個對象 `obj` 中獲取一個元方法的過程:
```
rawget(getmetatable(obj) or {}, "__" .. event_name)
```
對于一元操作符(取負、求長度、位反), 元方法調用的時候,第二個參數是個啞元,其值等于第一個參數。 這樣處理僅僅是為了簡化 Lua 的內部實現 (這樣處理可以讓所有的操作都和二元操作一致), 這個行為有可能在將來的版本中移除。 (使用這個額外參數的行為都是不確定的。)
* **"add":** `+` 操作。 如果任何不是數字的值(包括不能轉換為數字的字符串)做加法, Lua 就會嘗試調用元方法。 首先、Lua 檢查第一個操作數(即使它是合法的), 如果這個操作數沒有為 "`__add`" 事件定義元方法, Lua 就會接著檢查第二個操作數。 一旦 Lua 找到了元方法, 它將把兩個操作數作為參數傳入元方法, 元方法的結果(調整為單個值)作為這個操作的結果。 如果找不到元方法,將拋出一個錯誤。
* **"sub":** `-` 操作。 行為和 "add" 操作類似。
* **"mul":** `*` 操作。 行為和 "add" 操作類似。
* **"div":** `/` 操作。 行為和 "add" 操作類似。
* **"mod":** `%` 操作。 行為和 "add" 操作類似。
* **"pow":** `^` (次方)操作。 行為和 "add" 操作類似。
* **"unm":** `-` (取負)操作。 行為和 "add" 操作類似。
* **"idiv":** `//` (向下取整除法)操作。 行為和 "add" 操作類似。
* **"band":** `&` (按位與)操作。 行為和 "add" 操作類似, 不同的是 Lua 會在任何一個操作數無法轉換為整數時 (參見 [§3.4.3](#3.4.3))嘗試取元方法。
* **"bor":** `|` (按位或)操作。 行為和 "band" 操作類似。
* **"bxor":** `~` (按位異或)操作。 行為和 "band" 操作類似。
* **"bnot":** `~` (按位非)操作。 行為和 "band" 操作類似。
* **"shl":** `<<` (左移)操作。 行為和 "band" 操作類似。
* **"shr":** `>>` (右移)操作。 行為和 "band" 操作類似。
* **"concat":** `..` (連接)操作。 行為和 "add" 操作類似, 不同的是 Lua 在任何操作數即不是一個字符串 也不是數字(數字總能轉換為對應的字符串)的情況下嘗試元方法。
* **"len":** `#` (取長度)操作。 如果對象不是字符串,Lua 會嘗試它的元方法。 如果有元方法,則調用它并將對象以參數形式傳入, 而返回值(被調整為單個)則作為結果。 如果對象是一張表且沒有元方法, Lua 使用表的取長度操作(參見 [§3.4.7](#3.4.7))。 其它情況,均拋出錯誤。
* **"eq":** `==` (等于)操作。 和 "add" 操作行為類似, 不同的是 Lua 僅在兩個值都是表或都是完全用戶數據 且它們不是同一個對象時才嘗試元方法。 調用的結果總會被轉換為布爾量。
* **"lt":** `<` (小于)操作。 和 "add" 操作行為類似, 不同的是 Lua 僅在兩個值不全為整數也不全為字符串時才嘗試元方法。 調用的結果總會被轉換為布爾量。
* **"le":** `<=` (小于等于)操作。 和其它操作不同, 小于等于操作可能用到兩個不同的事件。 首先,像 "lt" 操作的行為那樣,Lua 在兩個操作數中查找 "`__le`" 元方法。 如果一個元方法都找不到,就會再次查找 "`__lt`" 事件, 它會假設 `a <= b` 等價于 `not (b < a)`。 而其它比較操作符類似,其結果會被轉換為布爾量。
* **"index":** 索引 `table[key]`。 當 `table` 不是表或是表 `table` 中不存在 `key` 這個鍵時,這個事件被觸發。 此時,會讀出 `table` 相應的元方法。
盡管名字取成這樣, 這個事件的元方法其實可以是一個函數也可以是一張表。 如果它是一個函數,則以 `table` 和 `key` 作為參數調用它。 如果它是一張表,最終的結果就是以 `key` 取索引這張表的結果。 (這個索引過程是走常規的流程,而不是直接索引, 所以這次索引有可能引發另一次元方法。)
* **"newindex":** 索引賦值 `table[key] = value` 。 和索引事件類似,它發生在 `table` 不是表或是表 `table` 中不存在 `key` 這個鍵的時候。 此時,會讀出 `table` 相應的元方法。
同索引過程那樣, 這個事件的元方法即可以是函數,也可以是一張表。 如果是一個函數, 則以 `table`、 `key`、以及 `value` 為參數傳入。 如果是一張表, Lua 對這張表做索引賦值操作。 (這個索引過程是走常規的流程,而不是直接索引賦值, 所以這次索引賦值有可能引發另一次元方法。)
一旦有了 "newindex" 元方法, Lua 就不再做最初的賦值操作。 (如果有必要,在元方法內部可以調用 [`rawset`](#pdf-rawset) 來做賦值。)
* **"call":** 函數調用操作 `func(args)`。 當 Lua 嘗試調用一個非函數的值的時候會觸發這個事件 (即 `func` 不是一個函數)。 查找 `func` 的元方法, 如果找得到,就調用這個元方法, `func` 作為第一個參數傳入,原來調用的參數(`args`)后依次排在后面。
## 2.5 – 垃圾收集
Lua 采用了自動內存管理。 這意味著你不用操心新創建的對象需要的內存如何分配出來, 也不用考慮在對象不再被使用后怎樣釋放它們所占用的內存。 Lua 運行了一個 _垃圾收集器_ 來收集所有 _死對象_ (即在 Lua 中不可能再訪問到的對象)來完成自動內存管理的工作。 Lua 中所有用到的內存,如:字符串、表、用戶數據、函數、線程、 內部結構等,都服從自動管理。
Lua 實現了一個增量標記-掃描收集器。 它使用這兩個數字來控制垃圾收集循環: _垃圾收集器間歇率_ 和 _垃圾收集器步進倍率_。 這兩個數字都使用百分數為單位 (例如:值 100 在內部表示 1 )。
垃圾收集器間歇率控制著收集器需要在開啟新的循環前要等待多久。 增大這個值會減少收集器的積極性。 當這個值比 100 小的時候,收集器在開啟新的循環前不會有等待。 設置這個值為 200 就會讓收集器等到總內存使用量達到 之前的兩倍時才開始新的循環。
垃圾收集器步進倍率控制著收集器運作速度相對于內存分配速度的倍率。 增大這個值不僅會讓收集器更加積極,還會增加每個增量步驟的長度。 不要把這個值設得小于 100 , 那樣的話收集器就工作的太慢了以至于永遠都干不完一個循環。 默認值是 200 ,這表示收集器以內存分配的“兩倍”速工作。
如果你把步進倍率設為一個非常大的數字 (比你的程序可能用到的字節數還大 10% ), 收集器的行為就像一個 stop-the-world 收集器。 接著你若把間歇率設為 200 , 收集器的行為就和過去的 Lua 版本一樣了: 每次 Lua 使用的內存翻倍時,就做一次完整的收集。
你可以通過在 C 中調用 [`lua_gc`](#lua_gc) 或在 Lua 中調用 [`collectgarbage`](#pdf-collectgarbage) 來改變這倆數字。 這兩個函數也可以用來直接控制收集器(例如停止它或重啟它)。
### 2.5.1 – 垃圾收集元方法
你可以為表設定垃圾收集的元方法, 對于完全用戶數據(參見 [§2.4](#2.4)), 則需要使用 C API 。 該元方法被稱為 _終結器_。 終結器允許你配合 Lua 的垃圾收集器做一些額外的資源管理工作 (例如關閉文件、網絡或數據庫連接,或是釋放一些你自己的內存)。
如果要讓一個對象(表或用戶數據)在收集過程中進入終結流程, 你必須 _標記_ 它需要觸發終結器。 當你為一個對象設置元表時,若此刻這張元表中用一個以字符串 "`__gc`" 為索引的域,那么就標記了這個對象需要觸發終結器。 注意:如果你給對象設置了一個沒有 `__gc` 域的元表,之后才給元表加上這個域, 那么這個對象是沒有被標記成需要觸發終結器的。 然而,一旦對象被標記, 你還是可以自由的改變其元表中的 `__gc` 域的。
當一個被標記的對象成為了垃圾后, 垃圾收集器并不會立刻回收它。 取而代之的是,Lua 會將其置入一個鏈表。 在收集完成后,Lua 將遍歷這個鏈表。 Lua 會檢查每個鏈表中的對象的 `__gc` 元方法:如果是一個函數,那么就以對象為唯一參數調用它; 否則直接忽略它。
在每次垃圾收集循環的最后階段, 本次循環中檢測到的需要被回收之對象, 其終結器的觸發次序按當初給對象作需要觸發終結器的標記之次序的逆序進行; 這就是說,第一個被調用的終結器是程序中最后一個被標記的對象所攜的那個。 每個終結器的運行可能發生在執行常規代碼過程中的任意一刻。
由于被回收的對象還需要被終結器使用, 該對象(以及僅能通過它訪問到的其它對象)一定會被 Lua _復活_。 通常,復活是短暫的,對象所屬內存會在下一個垃圾收集循環釋放。 然后,若終結器又將對象保存去一些全局的地方 (例如:放在一個全局變量里),這次復活就持續生效了。 此外,如果在終結器中對一個正進入終結流程的對象再次做一次標記讓它觸發終結器, 只要這個對象在下個循環中依舊不可達,它的終結函數還會再調用一次。 無論是哪種情況, 對象所屬內存僅在垃圾收集循環中該對象不可達且 沒有被標記成需要觸發終結器才會被釋放。
當你關閉一個狀態機(參見 [`lua_close`](#lua_close)), Lua 將調用所有被標記了需要觸發終結器對象的終結過程, 其次序為標記次序的逆序。 在這個過程中,任何終結器再次標記對象的行為都不會生效。
### 2.5.2 – 弱表
_弱表_ 指內部元素為 _弱引用_ 的表。 垃圾收集器會忽略掉弱引用。 換句話說,如果一個對象只被弱引用引用到, 垃圾收集器就會回收這個對象。
一張弱表可以有弱鍵或是弱值,也可以鍵值都是弱引用。 僅含有弱鍵的表允許收集器回收它的鍵,但會阻止對值所指的對象被回收。 若一張表的鍵值均為弱引用, 那么收集器可以回收其中的任意鍵和值。 任何情況下,只要鍵或值的任意一項被回收, 相關聯的鍵值對都會從表中移除。 一張表的元表中的 `__mode` 域控制著這張表的弱屬性。 當 `__mode` 域是一個包含字符 '`k`' 的字符串時,這張表的所有鍵皆為弱引用。 當 `__mode` 域是一個包含字符 '`v`' 的字符串時,這張表的所有值皆為弱引用。
屬性為弱鍵強值的表也被稱為 _暫時表_。 對于一張暫時表, 它的值是否可達僅取決于其對應鍵是否可達。 特別注意,如果表內的一個鍵僅僅被其值所關聯引用, 這個鍵值對將被表內移除。
對一張表的弱屬性的修改僅在下次收集循環才生效。 尤其是當你把表由弱改強,Lua 還是有可能在修改生效前回收表內一些項目。
只有那些有顯式構造過程的對象才會從弱表中移除。 值,例如數字和輕量 C 函數,不受垃圾收集器管轄, 因此不會從弱表中移除 (除非它們的關聯項被回收)。 雖然字符串受垃圾回收器管轄, 但它們沒有顯式的構造過程,所以也不會從弱表中移除。
弱表針對復活的對象 (指那些正在走終結流程,僅能被終結器訪問的對象) 有著特殊的行為。 弱值引用的對象,在運行它們的終結器前就被移除了, 而弱鍵引用的對象則要等到終結器運行完畢后,到下次收集當對象真的被釋放時才被移除。 這個行為使得終結器運行時得以訪問到由該對象在弱表中所關聯的屬性。
如果一張弱表在當次收集循環內的復活對象中, 那么在下個循環前這張表有可能未被正確地清理。
## 2.6 – 協程
Lua 支持協程,也叫 _協同式多線程_。 一個協程在 Lua 中代表了一段獨立的執行線程。 然而,與多線程系統中的線程的區別在于, 協程僅在顯式調用一個讓出(yield)函數時才掛起當前的執行。
調用函數 [`coroutine.create`](#pdf-coroutine.create) 可創建一個協程。 其唯一的參數是該協程的主函數。 `create` 函數只負責新建一個協程并返回其句柄 (一個 _thread_ 類型的對象); 而不會啟動該協程。
調用 [`coroutine.resume`](#pdf-coroutine.resume) 函數執行一個協程。 第一次調用 [`coroutine.resume`](#pdf-coroutine.resume) 時,第一個參數應傳入 [`coroutine.create`](#pdf-coroutine.create) 返回的線程對象,然后協程從其主函數的第一行開始執行。 傳遞給 [`coroutine.resume`](#pdf-coroutine.resume) 的其他參數將作為協程主函數的參數傳入。 協程啟動之后,將一直運行到它終止或 _讓出_。
協程的運行可能被兩種方式終止: 正常途徑是主函數返回 (顯式返回或運行完最后一條指令); 非正常途徑是發生了一個未被捕獲的錯誤。 對于正常結束, [`coroutine.resume`](#pdf-coroutine.resume) 將返回 **true**, 并接上協程主函數的返回值。 當錯誤發生時, [`coroutine.resume`](#pdf-coroutine.resume) 將返回 **false** 與錯誤消息。
通過調用 [`coroutine.yield`](#pdf-coroutine.yield) 使協程暫停執行,讓出執行權。 協程讓出時,對應的最近 [`coroutine.resume`](#pdf-coroutine.resume) 函數會立刻返回,即使該讓出操作發生在內嵌函數調用中 (即不在主函數,但在主函數直接或間接調用的函數內部)。 在協程讓出的情況下, [`coroutine.resume`](#pdf-coroutine.resume) 也會返回 **true**, 并加上傳給 [`coroutine.yield`](#pdf-coroutine.yield) 的參數。 當下次重啟同一個協程時, 協程會接著從讓出點繼續執行。 調用[`coroutine.yield`](#pdf-coroutine.yield) 會返回任何傳給 [`coroutine.resume`](#pdf-coroutine.resume) 的第一個參數之外的其他參數。
與 [`coroutine.create`](#pdf-coroutine.create) 類似, [`coroutine.wrap`](#pdf-coroutine.wrap) 函數也會創建一個協程。 不同之處在于,它不返回協程本身,而是返回一個函數。 調用這個函數將啟動該協程。 傳遞給該函數的任何參數均當作 [`coroutine.resume`](#pdf-coroutine.resume) 的額外參數。 [`coroutine.wrap`](#pdf-coroutine.wrap) 返回 [`coroutine.resume`](#pdf-coroutine.resume) 的所有返回值,除了第一個返回值(布爾型的錯誤碼)。 和 [`coroutine.resume`](#pdf-coroutine.resume) 不同, [`coroutine.wrap`](#pdf-coroutine.wrap) 不會捕獲錯誤; 而是將任何錯誤都傳播給調用者。
下面的代碼展示了一個協程工作的范例:
```
function foo (a)
print("foo", a)
return coroutine.yield(2*a)
end
co = coroutine.create(function (a,b)
print("co-body", a, b)
local r = foo(a+1)
print("co-body", r)
local r, s = coroutine.yield(a+b, a-b)
print("co-body", r, s)
return b, "end"
end)
print("main", coroutine.resume(co, 1, 10))
print("main", coroutine.resume(co, "r"))
print("main", coroutine.resume(co, "x", "y"))
print("main", coroutine.resume(co, "x", "y"))
```
當你運行它,將產生下列輸出:
```
co-body 1 10
foo 2
main true 4
co-body r
main true 11 -9
co-body x y
main true 10 end
main false cannot resume dead coroutine
```
你也可以通過 C API 來創建及操作協程: 參見函數 [`lua_newthread`](#lua_newthread), [`lua_resume`](#lua_resume), 以及 [`lua_yield`](#lua_yield)。