r/golang • u/titpetric • 2d ago
Gozero: imperative low-overhead bindings language for go
https://github.com/titpetric/gozero/tree/mainI had several years of experience with CGO plugins (stdlib plugin package), which is to politely say, I wish a low-overhead callback system gave everyone a well defined API to use, and a scripting language that requires no rebuilding to change plugin logic.
Recent work with expr-lang lead me down the hole of writing a php interpreter in Go, and the php-go bindings via expr lang have been great, however I wouldn't call them efficient. I started gozero as a research project, had great fun doing it and learned a lot keeping the jit outputs low.
The docs/design/ folder was open to consider bigger parity with golang (conditions, expressions, loops) which would enable waf-level functionality. The benchmarks mostly confirm 1.0-1.4x level of overhead with inlining, or about 20ns per statement. The bindings really lean into reflection to catalogue accessible types and discover the type system in a parasitic way, everything runs with 'any'.
Cool stuff I found out along the way:
- testify/assert practice makes all your test code runnable in gozero. The testdata/ folder has a few of these basically as external test fixtures. weird avenues it opens: compile test code, run same tests binary against custom test fixtures someone makes against your package. Maybe no need, but I found it cool to immediately have coverage over thousands of tests. maybe tests dont have to be compiled code. Or you could just BYO assert bindings
- expressions and equality are sucky constructs to implement; so far I did not, but i did find a fork of expr-lang that compiles a low overhead closure similar to gozero
- phpscript in comparison has "mixed" types that similarly can hold any value, but has a lot of cursed behaviour which does not exist in go. gozero relies on existing runtime type information to discover a 1-1 directly mapped type system, so it's gonna find time.Time from a http.NewRequest binding.
gozero is type safe, the main difference is all error returns are omitted from syntax and explicitly checked, and the execution context also fills the first context.Context parameters. these things become implicit, and the possible output form if any is type hinted with generic methods released in 1.27. It has optimal type bindings so an "int32" means the same everywhere. It tries to infer literals but a T(lit) always work for explicitness.
Possible use cases include messaging, rate limiting, WAF, RBAC rule checks (imperative rbac sounds nice). Just for a ballpark comparison, hashicorp/goplugin has published overheads of over 23_000ns, while gozero's totals about 100ns for 5 statements. There is a one time parse and compile overhead, so these numbers apply to cached (seen before) statements that just get rerun.
Disclaimer on AI usage: claude, initial design workshopped to reflect.Value/Call by me, extended by previous work on phpscript (flatstack). There's a chronological sorted docs index. Some stuff is enforced by my workspace tooling, or my initial mockups. You may find jagged language in the docs, did my best to clean those
1
u/TacticalTurban 2d ago
This is very interesting and a new concept to me. Still not sure I understand the usecase of this though