Photo by Anders Jildén on Unsplash
在系統中遷入一個動態輕巧的 scripting language 一直是個常見的設計。像 Rust 這樣的系統語言,雖然效能好,但是上手門檻較高。這時若是能遷入一個像 Lua 一樣動態型別,簡單易懂的語言,便能大幅提高系統彈性。
最近為了實做 actix-lua,研究了一下 Rust 跟 Lua 之間的介接,順便學了不少 Rust 跟 Lua 的設計,筆記在此。
Lua Binding 的選擇
Rust 現在有數套 Lua binding,比較常被人提到的是 lua, hlua 與 rlua 。
lua 基本上是直接把 Lua 的 C API 直接移植,沒有做多餘的包裝。所以需要絕對的效能的話,這可能是你的最佳選擇。不過安全性跟 UB 就要自己處理了。
hlua 提供是比較高階的介面,不讓你直接存取 Lua Stack,可以視為 lua API 上的再一層包裝。彈性較低,可能不適合某些需求。
rlua 是由知名遊戲工作室 chucklefish 開發。延續 Rust 對安全性的要求,設計介面時也是以安全性為最高原則。在使用 rlua 的 API 時,不會產生任何 UB(由於 lua API 跟內部的運作方式,這種 API 真的很難做到…)也許犧牲了一點效能,不過對於 Rust 的使用者來說,這樣的 tradeoff 應該是蠻值得的。
最後我用的是 rlua。
老問題:Lifetime & ownership
剛開始用 rlua 的時候,很容易就撞到 lifetime 的問題。不過,就跟一般使用 Rust 一樣:如果遇到很難解的 lifetime 跟 ownership 問題,很可能架構設計上就錯了。
rlua 作者很詳盡的解釋了 rlua 許多的設計概念,也深入剖析了 Lua 本身設計帶來的影響。不過,這些解釋大多分散在各 github issue 之中。這邊稍微整理一下幾個重點:
- rlua 提供的所有的 reference type,像是
Function
、Table
這些指向 Lua 內部元素的參照,都不應該被另外存起來。 - rlua 提供的 reference type 也不應該被存到任何 Lua 內的
userdata
或是 Rust callback 之中。 - 傳進 rlua 中的任何值都必須是
Send
(因為 lua 本身是Send
)跟'static
(因為你沒辦法知道 Lua 何時會 GC 掉這些值,難以找出精確的 lifetime。) - 3 的限制讓傳值給 lua 變的複雜許多。rlua 提供了
scope
API,可以用一個暫時的 scope 將不符合條件的值傳給 lua。傳過去的資料在 scope 結束後就會被銷毀,避免複雜的 lifetime 問題。
最後的成果:actix-lua
actix 是個 Rust 上的 actor framework,前陣子因為極高的效能而小有名氣。由於目前 Rust async/await 的支援還不算友善,actor model 算是能幫助簡化 async 程式的架構。
而 actix-lua 便是將 actix 與 Lua 整合,讓人能用 Lua 實做系統中某些需要彈性、熱抽換、方便修改的部分。想法來自於 openresty 這套整合 nginx 與 Lua 的軟體。目前主要被我用來處理即時資料的分析。
雖然寫的過程碰了蠻多壁,不過最後的成果我還算滿意。目前還在初步的階段,歡迎各種建議與回饋。