forgo is a fork of Go, currently synced to the Go 1.27 release branch, that adds Nim-style compile-time execution and AST macros, plus Rust-style ? error-propagation, a JavaScript-style throw for failing a function out with a new error, a Ruby/Perl-style postfix if for one-line guard clauses, SIMD enabled by default, and Dart/Flutter-style hot reload for a running program.
Fork of Go 1.27 Sources: .fgo and .go, mixed freely linux · macOS · windows Install: one line
throw — fail out with a new error04
Postfix if — one-line guard clauses05
# Linux / macOS
curl -fsSL https://github.com/lsegal/forgo/releases/latest/download/install.sh | sh
# Windows
irm https://github.com/lsegal/forgo/releases/latest/download/install.ps1 | iex
Both scripts install the latest GitHub release to ~/.forgo by default. After installing, point GOROOT at the install directory and add its bin/ to PATH — the script prints the exact commands — then run forgo (e.g. forgo build, forgo run).
Once installed, forgo upgrade (alias forgo update) re-runs the same install script over the current installation. The release tarball also includes forgopls in bin/, for the VS Code extension.
Files using forgo-specific syntax use the .fgo extension instead of .go. The toolchain treats the two identically everywhere — forgo build, forgo run, forgo test, module resolution — so a package can freely mix both. Each feature below links to its full rules in the docs.
//fgo:comptime functionsA function marked //fgo:comptime is ordinary Go: it type-checks, compiles, and can be called at runtime like any other function. When it is invoked directly as the initializer of a const declaration with constant-foldable arguments, the compiler evaluates it during type-checking and folds the result into a real Go constant — usable anywhere the language requires a constant expression.
//fgo:comptime func calculateFactorial(n int) int { result := 1 for i := 1; i <= n; i++ { result *= i } return result } // Evaluated entirely by the compiler. const factFive = calculateFactorial(5)
//fgo:macro — AST macrosA function marked //fgo:macro receives the unevaluated syntax tree of each of its call-site arguments and returns a syntax tree that is spliced into the caller’s code in place of the macro call, before type checking runs. Quote captures a template without evaluating it; Splice marks where a bound tree is substituted in.
//fgo:macro func double(x Node) Node { return Quote(func() { Splice(x) + Splice(x) }) } double(compute()) // expands to: compute() + compute()
? — error propagationexpr? evaluates expr, and if it produced a non-nil error, returns immediately from the enclosing function with that error; otherwise it evaluates to the non-error value, so it chains. It requires a function whose last result is a named error.
func loadConfig(path string) (name string, err error) {
f := open(path)? // returns early if open fails
cfg := parse(f)?.normalize()?
name = cfg.Name
return
}
throw — fail a function out with a new errorthrow EXPR returns immediately from the enclosing function, passing EXPR through as the last (error) result and a zero value for every other result. A bare string literal is sugar for errors.New. It is a contextual keyword, so Go code that uses throw as an identifier is unaffected.
func makeThing(s string) (*Thing, error) {
if s == "" {
throw "empty" // = throw errors.New("empty")
}
return &Thing{name: s}, nil
}
if — one-line guard clausesSTMT if COND is shorthand for if COND { STMT }. It works as a modifier on any statement kind that doesn’t introduce a new binding into the surrounding scope: an expression statement, a channel send, a plain assignment, return, throw, and break/continue/goto.
func check(s string) (n int, err error) {
throw "empty" if s == ""
return len(s), nil
}
continue if i%2 == 0
total += i if want(i)
comptime/embed and comptime/jsonTwo stdlib packages the compiler executes natively. comptime/embed reads files and inspects the filesystem at compile time — ReadFile, ReadDir, Exists, and Load, which embeds a whole file tree as an io/fs.FS. comptime/json marshals and unmarshals JSON there.
import ( "comptime/embed" "comptime/json" ) const banner = embed.ReadFile("banner.txt") const content = embed.Load("assets") const schema = json.Unmarshal[Schema]( embed.ReadFile("schema.json"))
Ordinary Go restricts const to bool, numeric, and string values. forgo also allows a struct or map (an ordered field-name-to-value mapping) and a slice or array (an ordered element list) to be a real constant. Field and index access on one is itself constant, so it composes with everything a scalar const already does.
type Schema struct {
Name string
Port int
Tags []string
}
const schema = json.Unmarshal[Schema](embed.ReadFile("schema.json"))
const name = schema.Name
var proof [schema.Port % 16]byte // an array length, like any const
forgo run --watch — hot reloadEdit a function, save, and the running program picks up the new code without restarting. Its goroutines keep running, its heap is untouched, and every package-level variable keeps the value it had — counters keep counting, caches stay warm, open files and connections stay open. This is the same thing Dart and Flutter do, done for an ahead-of-time compiled language.
Watch mode compiles with inlining disabled and runs on linux, macOS, and windows. A change to a function that is already running and never returns does not take effect, and a change to a struct’s layout, a function’s signature, or a package’s initialization is refused rather than applied.
$ forgo run --watch ./examples/hotreload pid 44081 — edit render() in examples/hotreload/main.fgo and save tick 1 tick 2 ... # edit render(), save forgo: reloaded 1 function in 11.9s GEN1 tick 104 (worker beat 51, 104 in history, up 51s) GEN1 tick 105
Note the tick count and the uptime: same process, new code.
The extension adds .fgo syntax highlighting plus a language client backed by forgopls, a build of gopls that understands ? well enough to type-check it correctly. //fgo:comptime and //fgo:macro are still compiler-only and show as false-positive diagnostics.
Using a coding agent on a forgo codebase? Point it at AGENTS.md. It tells the agent when to reach for ?, throw, postfix if, //fgo:comptime, and //fgo:macro instead of plain-Go idioms — including the rule that a forgo codebase should never hand-write return ..., err just to propagate or introduce an error.
The repo root is a Go distribution checkout and builds the way upstream Go does. Plain .go files compile as before, the standard library is Go 1.27’s, and forgo carries its own version — tracked in FORGO_VERSION — independent of the release it is synced against.