# gomacro: a Go interpreter whose install command resolves to a 2018 tag

> gomacro is a pure-Go interactive interpreter, debugger and macro system with an almost complete Go implementation and an unusually candid limitations list. Two facts decide whether it is usable today: go install resolves @latest to v2.7 from 2018-05-19 rather than the code pushed on 2026-09-01, and go.mod declares Go 1.23.0 while the README asks for 1.18+.

**cosmos72/gomacro** — Interactive Go interpreter and debugger with REPL, Eval, generics and Lisp-like macros

- Repository: https://github.com/cosmos72/gomacro
- Stars: 2,302 · Forks: 102
- Language: Go
- License: MPL-2.0
- Published: 2026-09-30 · Updated: 2026-09-30 · Language: en
- Canonical page: https://hysenlabs.com/projects/cosmos72-gomacro

## The install command resolves to a 2018 tag, not to the current code

The documented installation is one line:

```bash
go install github.com/cosmos72/gomacro@latest
```

And the release history is the problem. The three most recent tags are v2.5 on 2018-04-21, v2.6 on 2018-04-25 and v2.7 on 2018-05-19. There is no v2.8 and no v3. Go module resolution treats @latest as the highest release tag in the module, not as the tip of the default branch, so this command installs the May 2018 build. The repository's last push was on 2026-09-01, and the module's go.mod declares go 1.23.0, which is a directive that could not have been written in 2018. The commit history and the tag history have diverged, and the install command only sees the tags.

This is the first thing to establish before evaluating anything else about gomacro, because it means the artefact most users get and the artefact the author is working on are different. If you follow the README, you are running eight-year-old code and any bug fixed since then is still there. If you install from the branch tip, you are running code whose behaviour matches the current README and whose stability depends on how much testing the master branch has actually seen.

The two paths are not equivalent in risk. The tagged build has been in the wild since 2018 and its failure modes are known to whoever has used it. The tip is eight years of unreleased commits with no version marker, no changelog on the release page and no tag to diff against. The project does keep a CHANGELOG-style history in git and a full feature and limitation document under doc/, but the README does not tell a user which of the two they have just installed.

The default branch is master, so building from the tip means cloning and building the module from a local checkout rather than using go install against a version query.

## go.mod says Go 1.23.0 and a third dependency the README omits

Two smaller metadata problems, both checkable in the repository rather than the documentation, and both of the kind that decide whether a build works on the first attempt.

The first is the Go version. The README's prerequisites say Go 1.18+. The go.mod file says:

```go
go 1.23.0
```

These are not the same claim. A module declaring a go directive of 1.23.0 is declaring a language and toolchain floor, and a 1.22 toolchain will refuse or warn depending on your settings rather than quietly building. The prerequisite list has not been updated to match. Anyone on Go 1.18 or 1.19 who reads the README will find the build failing for a reason the README does not name.

The second is the dependency list. The README states that gomacro has two dependencies beyond the Go standard library, naming github.com/peterh/liner and golang.org/x/tools/go/packages. The go.mod direct requirements are three:

```go
require (
	github.com/mattn/go-runewidth v0.0.28
	github.com/peterh/liner v1.2.2
	golang.org/x/tools v0.36.0
)
```

github.com/mattn/go-runewidth is a direct requirement that the README does not mention. It is a width-aware string measurement library, which is consistent with a terminal REPL that needs to align its output, and it is not something the README's dependency statement accounts for. There is also a go.sum and four indirect requirements, github.com/clipperhouse/uax29/v2, golang.org/x/mod, golang.org/x/sync and golang.org/x/sys.

Neither of these is a criticism of the code. They are the ordinary drift that happens when a README is written once and a go.mod is maintained continuously, and they matter to an evaluator because both are the kind of thing that turns into an afternoon. Read go.mod as the authoritative dependency and toolchain statement, and treat the README's prerequisites section as a floor that was true at some earlier point.

## Two interpreter implementations live side by side

The repository listing is dense enough that its shape tells you the design better than the README does. Alongside main.go and go.mod there are two clusters of directories: classic/, cmd_classic/, fast/ and cmd/, plus ast2/, atomic/, base/, gls/, go/, imports/ and xreflect/.

The naming is not explained in the README, so the reading has to be inferred from what is referenced. The library embedding example imports github.com/cosmos72/gomacro/fast and calls fast.New(), so fast/ is the current interpreter API and classic/ is a second, older one. cmd/ and cmd_classic/ are the corresponding command front ends. Two implementations of the same thing in one repository is a normal artefact of a project that has changed approach and kept the old path available, and it means the public API you should read about is the fast package.

The supporting directories are more telling about how the thing works. gls/ is goroutine-local storage, which is what an interpreter needs in order to give each interpreted goroutine its own view of globals. xreflect/ is a reflection layer, which an interpreter needs because interpreted values are not Go values and reflect.Value has no way to represent them. imports/ is package loading, and ast2/ is AST handling, which is the macro machinery's substrate. None of this is described in the README beyond the mention of go/ast.Node as the macro input and output type.

There are also two directories prefixed with an underscore, _example/ and _experiments/, which the Go toolchain ignores. The underscore convention keeps them out of builds while leaving them in the repository, and their presence tells you the author keeps runnable experiments alongside the code. TrickyGo.md at the root is the third such artefact, a document about difficult Go semantics that the README never links.

For an evaluator, the useful conclusion is narrow: read fast/ for the API you would call, go.mod for what you must install, and doc/features-and-limitations.md for what will not work. The directory listing is a map, not documentation.

## The limitations list is the best part of the documentation

Current Status in the README is one word, Almost complete, followed by ten specific things that do not work. That list is more useful than any feature list in the project, and it is worth reading closely because each entry is a class of Go program that gomacro cannot run.

Runtime import of third-party packages only works on Linux, macOS and the BSDs, and is cumbersome on Windows and Android where it requires recompiling. Generics are not imported at all: when you import a package, standard library or third-party, its generic declarations do not come with it. Defining generic functions and types in interpreted code is experimental and incomplete, which means generics are the feature the project advertises and the feature with the least coverage.

Three entries concern types. Conversions to and from unsafe.Pointer are not supported, which removes anything built on the unsafe package, including a meaningful slice of performance-sensitive Go. Some corner cases involving interpreted interfaces are not implemented, specifically interface-to-interface type assertions and type switches. And some corner cases with recursive types may not work correctly, which is the kind of defect that produces a wrong answer rather than an error.

Two concern statement ordering. goto can only jump backward, not forward. And out-of-order code is under testing, with the concrete example that `var a = b; var b = 42` does not work, because the REPL executes code as soon as possible rather than waiting to see whether a later declaration would resolve a forward reference. Batch mode, which would read as much source as possible before executing, is described as in progress and useful mostly for whole files or directories.

The final line of that section points at doc/features-and-limitations.md as the full list, which means the ten above are a summary. For a project whose selling point is completeness, the honest failure inventory is the thing to read before deciding, and it is a better document than most projects' feature matrices.

The extensions section is also specific: an integrated debugger, configurable special commands listed with :help at the REPL and documented in fast/cmd.go, and untyped constants manipulable directly, with 1<<100 evaluated as an untyped integer rather than overflowing.

## Third-party imports are why the science use case works

The reason gomacro is more than a curiosity is one line in the README: imported libraries will be compiled, not interpreted, so they will be as fast as in compiled Go.

That distinction is the whole design. The interpreter handles your code; the library you import is compiled ahead of use. So if you work in Go with scientific libraries, you can import the same packages from the REPL, call them interactively, inspect the results and feed them to other functions in a single session, without paying interpretation cost on the parts that are actually expensive. The documented fit is an interactive tool to make science more productive, and the examples given are physics, bioinformatics and statistics.

The catch is the platform asymmetry, and it is a hard one. On Linux and macOS the import is immediate. On other platforms it requires restarting, and the limitations section narrows that further to Linux, macOS and the BSDs working, with Windows and Android described as cumbersome and requiring recompiling. If your work happens on a Windows workstation, the feature that makes gomacro interesting is the one you cannot use without recompiling it.

The Android entry in the supported platforms list says the same thing from the other direction: arm64 and arm, tested with Termux and the Go compiler distributed with it. It is on the list as a supported target for the interpreter, and the limitations list says package importing there is recompile-only. The two documents are consistent, and reading them together gives a clear picture: the interpreter itself is portable, the library-loading half is not.

There is also a dependency the README does not explain but the design requires. gomacro does not require a Go toolchain at runtime except in one specific case, the import of a third-party package at runtime. That exception is the cost of the compiled-library approach, and it is stated in a parenthetical in the opening paragraph, which is the least prominent place for the project's most consequential architectural constraint.

## Macros are go/ast in, go/ast out

gomacro started as an experiment to add Lisp-like macros to Go, and the macro system is the part that has aged best, because it is the part that depends least on the interpreter's completeness.

The mechanism is stated precisely. Macros are normal Go functions, special in one aspect: they are executed before compiling code, and their input and output is code, in the form of go/ast.Node. The README goes out of its way to distinguish this from C preprocessor macros, pointing out that in Lisp and Scheme, and now in Go, macros are regular functions written in the same language as the rest of the source, which can perform arbitrary computation, call any other function or library, read and write files and open network connections.

That framing matters because it explains why a macro cannot be implemented as a runtime interpreter feature. A macro runs at compile time on the AST, so it works even for the language constructs the interpreter does not support, and it works on a file that gomacro is not otherwise going to execute. Code generation is the stated use case, with an introduction in doc/code_generation.pdf.

The other execution path is separate and smaller. You can run a Go file directly with gomacro FILENAME.go, which works on every supported platform, or put #!/usr/bin/env gomacro at the top of a source file, mark it executable and run it, which works on Unix-like systems only. The shebang route is the one that makes gomacro feel like a scripting language, and its Unix-only nature is stated in the same sentence.

For a library consumer, the embedding API is small:

```go
package main
import (
	"fmt"
	"reflect"
	"github.com/cosmos72/gomacro/fast"
)
func RunGomacro(toeval string) reflect.Value {
	interp := fast.New()
	vals, _ := interp.Eval(toeval)
	// for simplicity, only use the first returned value
	return vals[0].ReflectValue()
}
```

fast.New() constructs the interpreter and Eval returns a slice of values, with ReflectValue() converting one back into a reflect.Value. The README also points at GitHub issue 13 for how to expose your own application's functions, variables, constants and types to the interpreter, which is the part that turns this from a toy into a debugging tool for a specific binary.

## MPL 2.0, the debugger, and the Jupyter alternative

Three things an evaluator should weigh before embedding gomacro, and the first is a licence note the README puts in an unusual place.

The README's own warning is that the gomacro license is MPL 2.0, which imposes some restrictions on programs that use gomacro, with a link to the Mozilla MPL 2.0 FAQ. That sentence is not boilerplate. MPL 2.0 is a file-level copyleft licence, so if you link gomacro into a program you distribute, the obligation attaches to the files that use it rather than to the whole work, and the FAQ is the place to check whether your packaging meets it. The restriction is manageable and it is real, which is more than most projects tell you. It is stated in the embedding section rather than in a dedicated licence section, so a reader skimming for the API gets the warning at the point where it applies.

The second is the debugger. It is listed first among the extensions compared to compiled Go, and the README describes gomacro as both an interactive interpreter and a debugger from the title onwards. No detail is given about breakpoints, stepping or watchpoints in what is documented here, so an evaluator who needs a debugger has to read the source or the doc/ directory. What is clear is the positioning: this is one of the two headline features, not an afterthought.

The third is the alternative worth naming, and the project names it itself. Gophernotes is a Go kernel for Jupyter notebooks and nteract that uses gomacro for Go code evaluation. That is a genuinely different approach to the same problem. A standalone binary gives you a line-editing REPL with TAB completion, Emacs-style key bindings by way of liner, and the debugger, with the control described as Ctrl+A or Home to jump to the start of the line and Ctrl+E or End to jump to the end. A Jupyter kernel gives you the same evaluation engine inside a notebook interface with cells, saved state and a graphical front end.

If you want a REPL, gomacro is the more direct tool. If you want reproducible exploratory analysis where the sequence of calls is the artifact, the notebook is the better container and gomacro is already the engine underneath it. The choice is not gomacro versus Gophernotes, it is which front end you run gomacro through.

The supported platform list is a final practical detail: Linux on amd64, 386, arm64, arm, mips and ppc64le, macOS and Windows on amd64 and 386, FreeBSD on amd64 and 386, and Android on arm64 and arm via Termux. The macOS 386 entry is annotated as 386 binaries running on an amd64 system, so Apple Silicon users are on the amd64 build under Rosetta, which interacts awkwardly with a project that also exists to help you run pre-Apple-Silicon software.

## Conclusion

Use gomacro if you want to poke at Go generics, write AST-level code generation macros, or attach an interactive shell to a scientific Go program, and if you are willing to build from a commit rather than from the documented install command. Do not follow the README's go install line and then judge the project by what you get, because @latest resolves to v2.7 from 2018-05-19 and nothing after it is tagged. Do not embed it in a distributed product without reading the MPL 2.0 terms, since the README itself notes the licence imposes restrictions on programs that use gomacro. Verify four things. Build from the tip of master rather than the tag, and confirm the resulting binary behaves the way the README describes. Check whether your Go toolchain is at least 1.23.0, because go.mod says so even though the prerequisites list says 1.18. Confirm your platform is Linux, macOS or a BSD if you intend to import third-party packages at runtime, since Windows and Android are documented as needing recompilation. Then read doc/features-and-limitations.md, because the ten limitations in the README are a summary and the full list is longer. The deciding detail is that this project's value is its honest accounting of what an interpreter cannot do, not the interpreter itself.

## FAQ

### How do I install gomacro?

The README documents go install github.com/cosmos72/gomacro@latest, which downloads, compiles and installs gomacro and its dependencies. Go 1.18 or newer is listed as the prerequisite, although go.mod declares go 1.23.0. Because the newest release tag is v2.7 from 2018-05-19, that command installs the tagged build rather than the code pushed on 2026-09-01.

### What are gomacro's main limitations?

The README lists ten. Third-party library import at runtime only works on Linux, macOS and the BSDs, and needs recompiling elsewhere. Generics are not imported with packages, and defining generic functions and types in interpreted code is experimental. Conversions to and from unsafe.Pointer are unsupported, interface-to-interface assertions and type switches are not implemented, some recursive types may not work, goto only jumps backward, and out-of-order declarations such as var a = b; var b = 42 do not work. The full list is in doc/features-and-limitations.md.

### What licence is gomacro under and does it affect my program?

gomacro is MPL 2.0, and the README states directly that this imposes some restrictions on programs that use gomacro, linking to the MPL 2.0 FAQ. MPL 2.0 is file-level copyleft, so the obligation attaches to the files that use gomacro rather than to the whole work, and the FAQ is the reference for whether a given distribution meets the terms.

### Can I use gomacro as a library inside my Go program?

Yes. Import github.com/cosmos72/gomacro/fast, construct an interpreter with fast.New() and call Eval, which returns a slice of values that you can convert with ReflectValue(). The README also points at GitHub issue 13 for exposing your own application's functions, variables, constants and types to the interpreter.

### How do gomacro macros work?

They are normal Go functions with one special property: they run before code is compiled, and their input and output is code in the form of go/ast.Node. Because they operate on the AST rather than on evaluated values, they can be used for code generation and are not limited by the interpreter's runtime limitations. An introduction is in doc/code_generation.pdf.

## Sources

- [cosmos72/gomacro on GitHub](https://github.com/cosmos72/gomacro)
- [Issues](https://github.com/cosmos72/gomacro/issues)
- [License: MPL-2.0](https://github.com/cosmos72/gomacro/blob/master/LICENSE)
- [README](https://github.com/cosmos72/gomacro/blob/master/README.md)
- [Releases](https://github.com/cosmos72/gomacro/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/cosmos72-gomacro
