yaegi:讓你的 Go 代碼擁有動態腳本能力

在 Go 語言的世界裏,靜態編譯是其一大特色,能夠保證性能和安全性。然而,有些場景下,我們希望像 Python 或 JavaScript 一樣,支持運行時動態執行代碼,比如插件化架構、規則引擎、腳本解釋器等。今天,我們來介紹一個強大的 Go 解釋器庫——yaegi,它能讓 Go 代碼在運行時執行動態腳本。

yaegi 簡介

yaegi 是一個用 Go 語言編寫的 Go 解釋器。它可以在運行時解析和執行 Go 代碼,而無需重新編譯。

yaegi 適用於以下場景:

安裝 yaegi

使用 go install 安裝 yaegi CLI 工具:

go install github.com/traefik/yaegi/cmd/yaegi@latest

安裝完成後,可以在終端直接使用 yaegi 作爲 Go 代碼的 REPL:

➜  ~ yaegi> 
fmt.Println("hello world")
hello world

或者在 Go 項目中引入 yaegi 庫:

go get github.com/traefik/yaegi

使用 yaegi 執行 Go 代碼

1. 直接執行 Go 代碼

最基礎的用法是直接解析並執行 Go 代碼:

package main
import (
	"fmt"
	"github.com/traefik/yaegi/interp"
	"github.com/traefik/yaegi/stdlib"
)
func main() {
	i := interp.New(interp.Options{}) // 創建解釋器實例
	i.Use(stdlib.Symbols)             // 加載標準庫
	_, err := i.Eval(`
package main
import "fmt"
func main() {
    fmt.Println("Hello from Yaegi!")
}
`)
	if err != nil {
		fmt.Println("Error:", err)
	}
	// 調用 main 函數
	_, err = i.Eval("main()")
	if err != nil {
		fmt.Println("Error:", err)
	}
}
  1. 運行用戶自定義函數

我們可以在 yaegi 裏定義並執行 Go 函數:

package main
import (
	"fmt"
	"github.com/traefik/yaegi/interp"
	"github.com/traefik/yaegi/stdlib"
)
func main() {
	// 創建解釋器實例
	i := interp.New(interp.Options{})
	i.Use(stdlib.Symbols) // 加載標準庫
	// 解釋並執行Go代碼
	code := `
package main
func Add(a, b int) int {
    return a + b
}
`
	_, err := i.Eval(code) // 解析代碼
	if err != nil {
		fmt.Println("Error:", err)
		return
	}
	// 獲取Add函數
	v, err := i.Eval("Add")
	if err != nil {
		fmt.Println("Error:", err)
		return
	}
	// 將v轉換爲函數
	addFunc, ok := v.Interface().(func(int, int) int)
	if !ok {
		fmt.Println("Error: 類型轉換失敗")
		return
	}
	// 調用Add函數
	result := addFunc(3, 4)
	fmt.Println("Result:", result) // 輸出:result
}

關鍵點解析

1. 初始化 yaegi 解釋器

i := interp.New(interp.Options{})
i.Use(stdlib.Symbols)
  1. 解析代碼
_, err := i.Eval(code)

3. 獲取並調用 Add 函數

v, err := i.Eval("Add")
addFunc, ok := v.Interface().(func(int, int) int)

動態加載插件

yaegi 也可以用來動態加載 Go 插件,例如我們可以創建一個插件 plugin.go:

package main
import "fmt"
// 定義插件函數
func Hello(name string) {
    fmt.Println("Hello,", name)
}
// 計算平方值
func Square(n int) int {
    return n * n
}

此插件文件定義了兩個函數:

主程序 main.go,加載插件並調用函數

yaegi 解釋器會動態解析 plugin.go,並在運行時調用 Hello 和 Square 函數。

main.go(主程序,動態加載 plugin.go):

package main
import (
	"fmt"
	"os"
	"github.com/traefik/yaegi/interp"
	"github.com/traefik/yaegi/stdlib"
)
func main() {
	// 讀取插件代碼
	pluginCode, err := os.ReadFile("plugin.go")
	if err != nil {
		fmt.Println("Error reading plugin file:", err)
		return
	}
	// 創建解釋器實例
	i := interp.New(interp.Options{})
	i.Use(stdlib.Symbols) // 加載標準庫
	// 解析插件代碼
	_, err = i.Eval(string(pluginCode))
	if err != nil {
		fmt.Println("Error evaluating plugin:", err)
		return
	}
	// 獲取Hello函數
	v, err := i.Eval("Hello")
	if err != nil {
		fmt.Println("Error finding Hello function:", err)
		return
	}
	// 轉換Hello爲可調用的函數
	helloFunc, ok := v.Interface().(func(string))
	if !ok {
		fmt.Println("Error: 類型轉換失敗")
		return
	}
	// 調用Hello函數
	helloFunc("Yaegi")
	// 獲取Square函數
	v, err = i.Eval("Square")
	if err != nil {
		fmt.Println("Error finding Square function:", err)
		return
	}
	// 轉換Square爲可調用的函數
	squareFunc, ok := v.Interface().(func(int) int)
	if !ok {
		fmt.Println("Error: 類型轉換失敗")
		return
	}
	// 調用Square函數
	fmt.Println("Square(5):", squareFunc(5))
}

代碼解析

1. 首先 plugin.go 作爲動態插件,定義了 Hello 和 Square 函數。

2. 然後在主程序文件 main.go 中使用 yaegi 加載 plugin.go,並在運行時執行函數。

3. 調用 Eval 方法解析並獲取插件中的函數,然後通過 Interface() 轉換爲 Go 語言的可調用函數。

🚀 這樣,你就可以在不重新編譯的情況下,動態加載和執行 Go 插件了!真是泰褲辣!!!😍😍😍

安全性與限制

雖然 yaegi 提供了強大的動態執行能力,但也需要注意安全問題,比如避免執行不受信任的代碼。此外,yaegi 並不完全支持 Go 語言的所有特性,比如 cgo 和某些高級類型推導機制。如果你想在生產環境使用 yaegi,建議結合代碼沙箱機制權限管理,避免執行未經審查的代碼。

總結

yaegi 讓 Golang 擁有了動態執行的能力,使得我們可以在不重新編譯的情況下,動態加載和運行 Go 代碼。它適用於規則引擎、插件系統和 REPL 等場景,但在生產環境使用時需要做好安全性控制。

如果你有動態執行 Go 代碼的需求,不妨試試 yaegi

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/Ytii6RaMjGfGlmJPKR53bA