Mino:將受 Clojure 啟發的易用性帶入可移植的 C99
在可嵌入腳本語言的領域中,開發者常常需要在類似 Lua 的輕量特性與現代函式語言強大的資料處理易用性之間做取捨。Mino 旨在透過提供一個以可移植 C99 編寫、受 Clojure 啟發的 Lisp,來彌合這個差距,並特別設計成能以最小阻力嵌入現有主機應用程式。
結合小記憶體佔用、持久不可變資料結構與基於能力的安全模型,Mino 為需要彈性、沙盒化腳本層且不想承擔 JVM 或龐大執行時負擔的開發者提供了一個有說服力的替代方案。
分層嵌入方法
Mino 以「層」的概念設計,讓開發者能選擇所需的佔用空間與功能之間的精確平衡。這種模組化確保語言仍然「足夠小以便嵌入」且「足夠快以發揮效用」。
1. Floor(最低層)
這是執行時最精簡的版本,專注於絕對必要的部分:讀取器、求值器、垃圾回收器、持久集合、數值運算與基礎巨集。它不包含 core.clj 的求值,因而佔用空間僅約 613 KB,且初始化時間極快,僅 0.23 ms。
2. Standard(完整 Clojure 表面)
在 Floor 層之上,Standard 版加入了 Clojure 腳本作者所期望的功能,包括正規表達式、巨數、多方法、協議與轉換器。此層的佔用空間略增至 728 KB,初始化成本為 5.3 ms。值得注意的是,該層仍屬沙盒;除非主機明確授權,否則不具備 I/O 能力。
3. Standalone(完整套件)
Standalone 層是完整套件,包含 I/O、檔案系統存取、軟體交易記憶體(STM)、代理、非同步功能、捆綁的 clojure.* 標準函式庫,以及 REPL。其大小為 987 KB,初始化時間 5.5 ms,提供完整的類 Clojure 環境體驗。
效能與可移植性
Mino 的主要優勢之一是冷啟動效能。在 x86_64 Linux 上,Floor 層的實際冷啟動時間(50 次執行的中位數)為 1.04 ms。這與 Lua 5.5(0.78 ms)相當,且優於 Janet(1.95 ms),因此適合對程序生成時間要求嚴格的環境。
由於採用可移植的 C99 編寫,Mino 可嵌入幾乎任何具備 C FFI 的主機,包括 C++、Rust、Go、Java、.NET、Swift 與 Zig。它不需要外部執行時、JIT 或守護程序;主機進程完全掌控執行。
架構支柱
隔離執行時與記憶體安全
每個 Mino 執行時皆作為獨立的失敗領域,擁有專屬的堆與垃圾回收器。為防止共享可變狀態與潛在的競爭條件,執行時之間的資料傳遞皆透過深度拷貝處理。此架構允許單一程序在完全隔離的情況下承載多個租戶。
基於能力的互操作門檻
安全性已深植於執行時設計中。全新的 Mino 執行時從零能力開始——沒有檔案系統存取、沒有網路存取,也沒有環境狀態。嵌入者必須明確授權每項能力,並註冊腳本允許呼叫的特定主機類型與方法。
受 Clojure 啟發的易用性
對於腳本作者而言,Mino 提供了使 Clojure 強大的高階抽象:
- 持久不可變資料結構:消除別名錯誤與「誰改變了我的表」的驚訝。
- 惰性序列:允許對可能無限的資料流進行高效處理。
- 尾呼叫優化:確保遞迴函式(如
drain操作)能在恆定的堆疊空間中執行。 - REPL 驅動工作流程:支援即時檢查與執行時設定變更,無需重新編譯主機應用程式。
實務實作:三角色工作流程
Mino 定義了三個主要角色之間的明確職責分離:
- 應用程式開發者:嵌入執行時,並透過授權特定能力來定義安全邊界。
- C++ 工程師:將主機類型暴露給腳本。主機物件以帶類型標籤的句柄包裝,並自動進行 GC 清理。由於 API 使用直接的值指標而非堆疊索引,避免了傳統 C 基礎嵌入 API 常見的越界錯誤。
- 腳本作者:使用 Lisp 語法撰寫業務邏輯。它們與主機物件互動時彷彿是原生 Mino 類型,利用關鍵字作為資料存取器,並將集合作為可呼叫的謂詞。
潛在使用案例
鑑於其設計,Mino 特別適用於:
- 規則引擎與政策:將主機狀態暴露給 Mino 謂詞,以實現宣告式業務邏輯。
- 插件系統:載入使用者撰寫的腳本,嚴格控制資源限制且無環境存取。
- 資料管線:在持久集合上組合
map、filter與reduce,利用結構共享。 - 遊戲腳本:為玩家提供可編程的主控台,具沙盒與步數限制,以防止無限迴圈或崩潰。
- 互動式主控台:在應用內嵌入 REPL,以即時除錯與執行時設定。