Backend Development 16 min read

Using Go as a Scripting Language with Yaegi: Concepts, Quick Start, and Comparative Evaluation

The article explains how Go, traditionally a compiled language, can serve as a scripting language using the Yaegi interpreter—detailing its syntax‑compatible design, easy struct integration, quick‑start example, performance comparison with gopher‑lua and Tengo, and practical engineering guidelines for safe embedding.

Tencent Cloud Developer
Tencent Cloud Developer
Tencent Cloud Developer
Using Go as a Scripting Language with Yaegi: Concepts, Quick Start, and Comparative Evaluation

Go, as a compiled language, is often used for backend services. Because its early designers were seasoned C users, Go retains many C programming habits, making it attractive to C/C++ and PHP developers. While compiled languages usually have superior performance in concurrent environments, the article explores why Go can also be treated as a scripting language.

1. What kinds of languages can be considered scripting languages?

Programming languages can be classified by execution model: compiled languages are translated to machine code before execution, while interpreted languages run via an interpreter at runtime. Typical compiled languages include Assembly, C, C++, Objective‑C, Go, Rust, etc. Interpreted languages include JavaScript, PHP, Shell, Python, Lua, etc. A "script language" is defined as a language designed for small programs or logic that is executed by a pre‑set interpreter.

Although interpreted languages naturally serve as script languages, developers often try to turn compiled languages into script languages for flexibility.

2. Why write scripts in Go?

Embedding a script language allows runtime modification of certain logic (e.g., data validation, filtering) without rebuilding the whole binary. The author mentions two mature Go‑based script solutions: yaegi and gopher‑lua . The focus here is on yaegi , which follows official Go syntax (1.16/1.17) and offers three main advantages:

yaegi fully complies with official Go syntax, requiring no new language learning (generic support is still missing).

It can call native Go libraries and extend third‑party packages.

It allows direct struct passing between the host program and the script, simplifying integration.

3. Quick Start

Below is a minimal Fibonacci implementation written as a Go plugin:

package plugin

func Fib(n int) int {
    return fib(n, 0, 1)
}

func fib(n, a, b int) int {
    if n == 0 { return a }
    if n == 1 { return b }
    return fib(n-1, b, a+b)
}

To evaluate this code with yaegi :

package main

import (
    "fmt"
    "github.com/traefik/yaegi/interp"
    "github.com/traefik/yaegi/stdlib"
)

func main() {
    intp := interp.New(interp.Options{}) // initialize interpreter
    intp.Use(stdlib.Symbols)               // expose standard library symbols
    intp.Eval(src)                         // src is the string above
    v, _ := intp.Eval("plugin.Fib")
    fib := v.Interface().(func(int) int)
    fmt.Println("Fib(35) =", fib(35))
}

const src = `
package plugin

func Fib(n int) int { return fib(n, 0, 1) }

func fib(n, a, b int) int {
    if n == 0 { return a }
    if n == 1 { return b }
    return fib(n-1, b, a+b)
}`

4. Custom Data Structure Transfer

By adding a custom struct to the interpreter’s symbol table, scripts can receive and manipulate native Go structs. Example:

type Route struct {
    XIndexes []int
    YIndexes []int
}

intp.Use(map[string]map[string]reflect.Value{
    "github.com/Andrew-M-C/go.util/slice/slice": {
        "Route": reflect.ValueOf((*slice.Route)(nil)),
    },
})

This enables the script to use Route directly.

5. Yaegi Support for Third‑Party Libraries

The interpreter links symbols from the provided symbol table. By calling intp.Use(stdlib.Symbols) , all standard library symbols become available, while custom packages can be added similarly. However, only symbols defined beforehand can be used; dynamic loading is not supported.

6. Comparison with Other Script Solutions

The author compares three Go‑based script engines:

gopher‑lua : highest raw computation performance.

yaegi : uses real Go syntax, moderate performance (≈1/4–1/5 of gopher‑lua), easy struct passing.

tengo : claims high performance but in the author’s tests performed poorly, especially for trivial scripts.

Performance tests (simple addition, condition check, Fibonacci) show:

gopher‑lua dominates in CPU‑bound tasks.

yaegi is stable and faster than tengo.

tengo incurs large overhead for script startup/teardown.

7. Engineering Considerations

When embedding yaegi in production, the author recommends:

Restrict accessible packages (e.g., remove os/* , net/* , log , io/* , database/* , runtime ).

Run script calls in isolated goroutines with recover to catch panics.

Avoid exposing host program variables directly; copy parameters when passing to scripts.

Optionally forbid the go keyword to prevent scripts from spawning new goroutines.

Enforce execution time limits to avoid infinite loops.

Author Bio

Zhang Min – Senior Backend Engineer at Tencent, with extensive experience in embedded and cloud service development, over 100 technical articles, Cloud+ Community Top‑50 original author, and instructor for Tech Creation 101 Season 2.

Recommended Reading

Node.js memory leak causes

Raft distributed consensus tutorial

Pulsar vs RocketMQ vs Kafka vs InLong‑TubeMQ

gRPC practical guide for Golang and PHP

backend developmentGoembeddingscriptingPerformance ComparisoninterpreterYaegi
Tencent Cloud Developer
Written by

Tencent Cloud Developer

Official Tencent Cloud community account that brings together developers, shares practical tech insights, and fosters an influential tech exchange community.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.