Open-source project
MadAppGang/dingo avatar
MadAppGang/dingo

Dingo: a Go transpiler that adds Result types, `?` propagation and pattern matching

A meta-language for Go that adds Result types, error propagation (?), and pattern matching while maintaining 100% Go ecosystem compatibility

1,910 stars38 forksGoNOASSERTION

At a glance

What is it?
Dingo is a source-to-source compiler that turns a Go-like meta-language into plain Go. The pitch is real ergonomics for error handling and sum types without a new runtime, but the project is still pre-1.0 and its own README carries the warning signs.
Who is it for?
Adopt Dingo if you are comfortable reading generated Go and can absorb a pre-1.0 toolchain whose v1.0 target is Q1 2026 and whose release cadence is still measured in months. Do not adopt it for a production codebase that other teams must build without the `dingo` binary, because the transpiled `.go` files are the artifact everyone else has to trust.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Yes. The repository last received commits 6 days ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.

DEEP OPEN-SOURCE ANALYSIS

The problem Dingo targets: `if err != nil` and nil panics

Go's error handling is explicit by design, and that explicitness is the thing Dingo attacks. The README's opening argument is a count: 47 `if err != nil` blocks in a single file. Whether or not that number matches your codebase, the shape of the complaint is familiar. Business logic gets buried under repeated guard clauses, and the compiler gives you no help when you forget one.

The second complaint is nil. Go pointers can be nil at any time, and the README frames the consequence bluntly: a forgotten check turns into a production panic. Dingo's answer is an `Option<T>` type plus `?.` and `??` operators, so the absence of a value becomes something the type system tracks rather than something you remember to test for.

The audience is Go teams who want Rust-style ergonomics without leaving the Go toolchain. Dingo does not ship a runtime, a garbage collector, or a package manager. It emits `.go` files, and those files are supposed to be readable by anyone who has never heard of Dingo. That constraint is what separates it from a new language and makes the whole idea plausible in a team setting.

How the transpiler works: `.dingo` in, `.go` out, no runtime

The repository is laid out as a compiler project. The topics list includes `ast`, `compiler`, `parser`, and `language-server`, and the README's own description of the project is "a language that compiles to clean, idiomatic Go code." The `cmd/dingo` directory holds the compiler entry point that the installation instructions build.

Data flow is one-directional. You write `.dingo` source. The compiler parses it into an AST, applies the features you have enabled, and writes Go source. That Go source is then compiled by the normal Go toolchain. The README is explicit that this is not a franken-runtime, and that disabling all plugins leaves you running pure Go.

The plugin framing matters more than it first appears. Dingo presents features as opt-in: enable pattern matching if you want it, leave it off if you do not. The README even invites you to "fork the plugin and prove it" if you think you can do better. That is a design commitment to keeping the core small and the extensions separable, and it is the reason the project can claim that every Go feature still works. Go 1.24 adds something, and the README says you get it day one, because the output is ordinary Go and the input is a superset.

Source maps are listed as one of the things Dingo adds over Borgo, which matters for debugging: when a generated Go file panics, you want the stack trace to point back at the `.dingo` line, not the emitted line.

Getting it running: clone, build, and three subcommands

There is no package manager install path in the README. You clone and build:

git clone https://github.com/MadAppGang/dingo.git cd dingo go build -o dingo ./cmd/dingo export PATH=$PATH:$(pwd)

The last line is marked optional, but without it you are invoking `./dingo` by path.

Three subcommands cover the workflow, and the README maps each to its Go equivalent. `dingo build hello.dingo` transpiles and compiles to a binary, like `go build`. `dingo run hello.dingo` transpiles and runs immediately, like `go run`. `dingo go hello.dingo` transpiles only, generating `.go` files without compiling. That third one is the command you want when you are evaluating Dingo, because it is the only one that shows you the artifact your team would actually have to maintain.

A minimal `hello.dingo` is just Go with a different extension: a `package main`, an `import "fmt"`, a `func main`. That is deliberate. The README's framing is TypeScript for Go, and the entry cost is supposed to be near zero because the syntax you already know keeps working.

For tooling, the README points at a `docs/features/` directory and an `examples/` directory, and the release notes for v0.9.0 mention `dingo watch` alongside cross-file type resolution. The README does not document the watch command's flags, so treat that as something to read from the source rather than from the docs.

The feature set: enums, `match`, `?`, `?.` and `??`

The README's at-a-glance table lists sum types, pattern matching, error propagation, tuples and safe navigation as working. The code samples show what that looks like in practice.

An `enum` declares variants with payloads, so `enum Result { Ok(value: int), Error(message: string) }` is a tagged union rather than Go's integer constant enum. A function can return that type directly, and `match` destructures it with arms like `Ok(value) => ...` and `Error(msg) => ...`. The README claims exhaustiveness checking, which is the part that actually changes how you write code: the compiler tells you when you have missed a case.

The `?` operator is the error propagation shorthand. The README's comparison table lists it against the 47-guard-clause problem, and describes the result as clean propagation rather than a new error model. What it transpiles to is Go's existing `if err != nil { return ... }` pattern, which is why the README can claim zero runtime overhead.

Safe navigation chains: `city := user?.address?.city?.name ?? "Unknown"`. The README notes this works with method calls and with Go pointers, giving `timeout := config?.database?.timeout ?? 30` as an example, and shows chained defaults across several sources ending in `?? "light"`. That last example is the one worth scrutinising, because chained coalescing is where evaluation order and short-circuiting get subtle, and the README does not spell out the semantics.

Functional helpers (`map`, `filter`, `reduce`) are shown on a slice of ints. There is no discussion of allocation behaviour or whether these are generic, so the cost of using them on hot paths is not documented.

Where the README oversells, and what the numbers actually are

Two figures stand out and neither is sourced. The README claims "67% less error handling boilerplate" and "78% code reduction with sum types." There is no methodology, no repository, no line-counting script referenced. The README also says the examples come from the project's test suite, which is a stronger claim than a hand-written demo, but the truncated material does not show those tests. Treat both percentages as marketing until you measure your own file.

The Borgo comparison deserves the same scepticism. The README says Borgo "already proved you can transpile to Go successfully" and cites 4.5k stars. Star counts measure attention, not correctness, and a transpiler that works for Borgo's feature set does not automatically generalise to Dingo's. The genuinely useful part of that paragraph is the differentiator: Dingo claims better IDE integration and source maps, and it is implemented in pure Go rather than whatever Borgo uses. Those are checkable claims. The star count is not evidence of anything.

There is also a licensing discrepancy worth flagging. The README carries an Apache 2.0 badge and links to a LICENSE file, but the repository metadata reports the licence as NOASSERTION, which is what GitHub shows when it cannot match the file to a known licence. That could be a formatting issue in the file, or it could be something else. Either way, if you need Apache 2.0 terms, read the LICENSE file yourself. This is not legal advice; it is a reason to look.

Limitations: pre-1.0, phase-based, and a toolchain everyone needs

The README states the project is in active development and targets v1.0 in Q1 2026. The release history backs up the pre-1.0 status: v0.5.0 in December 2025, v0.6.0 a day later, v0.9.0 in January 2026. That is a fast-moving surface, and fast-moving surfaces break. A feature documented in the README today may have moved or changed shape by the time you pin a version.

The README also notes that golden tests were updated to use current API patterns, naming `.MustSome()` and `.MustOk()`. That is a small detail with a large implication: the API has already changed enough that tests needed rewriting. If you adopt Dingo, budget for that kind of churn in your own code.

The deeper structural limitation is the one the README does not address. Dingo's output is Go, but the input is not. Anyone who needs to modify the code needs the `dingo` binary, or needs to edit generated `.go` files and accept that the next transpile will overwrite them. There is no documented round-trip story. For a solo project or a team that has standardised on Dingo, this is fine. For a shared repository where a contractor, an auditor, or an upstream maintainer needs to send a patch, it is a real friction point.

The final caveat is tooling. The README describes full LSP integration with a gopls proxy and auto-rebuild on save. A proxy in front of gopls is a layer that can desynchronise, and the README does not describe what happens when gopls is a version the proxy does not expect. That is the kind of failure you discover on a Tuesday afternoon.

The real alternative: Borgo, and how the two differ

The README names its own predecessor, and it is the honest comparison to make. Borgo is a Rust-inspired language that compiles to Go, and the README credits it with proving the transpile-to-Go approach works.

The difference is in what surrounds the compiler. Borgo's selling point is the language itself: Rust-like syntax and semantics, with Go as the compilation target. Dingo's selling point is narrower and more pragmatic. It keeps Go syntax as the baseline and layers specific features on top, so a `.dingo` file with no advanced features is very close to a `.go` file. That lowers the adoption cost for an existing Go team, because the migration is incremental rather than a rewrite into unfamiliar syntax.

The second difference is tooling. The README explicitly claims better IDE integration and source maps than Borgo, and lists `gopls` and `language-server` among the repository topics. For a transpiled language, editor support is not a nice-to-have. If your editor cannot jump to a definition or show a type on hover, you are reading generated Go to debug your own code, and the ergonomics argument collapses.

The third difference is implementation language. Dingo is pure Go. That means anyone on your team who already writes Go can read the compiler, and `go build -o dingo ./cmd/dingo` is the entire build. For a project you might need to patch or fork, that is a meaningful reduction in the number of skills required.

What Borgo has that Dingo does not, based on this material, is age. Borgo has been around long enough to accumulate users and survive contact with real codebases. Dingo's changelog starts in December 2025.

Maintenance cost and the licence question

Upgrade cost has two components here. The first is the Dingo version itself. With releases landing weeks apart and an API that has already required test rewrites (`.MustSome()`, `.MustOk()`), pinning a version and reading `CHANGELOG.md` before each bump is the minimum. The README points at that file for details, and the release notes name the specific changes, which is more than many pre-1.0 projects bother to do.

The second component is the generated Go. Every time you upgrade the compiler, the emitted code can change, which means diffs in files you did not write. If those files are committed, your review process absorbs that noise. If they are generated at build time, your CI needs the `dingo` binary installed and version-pinned, and you need to decide whether CI compiles `.dingo` or the checked-in `.go`.

The README does not describe a `go generate` integration or a module-aware build mode. It documents three subcommands that take a file argument. How that scales to a multi-package module is not covered in the material available, and that is a gap worth resolving before you plan a migration.

On licensing, the README badge says Apache 2.0 and links to `LICENSE`, while GitHub's metadata reports NOASSERTION. Apache 2.0 includes an explicit patent grant and requires attribution, which matters if you redistribute generated code. Since the two sources disagree, read the file. Nothing here is legal advice, and the discrepancy is exactly the kind of thing that should be resolved by a human reading the actual text rather than a badge.

Editorial conclusion

Adopt Dingo if you are comfortable reading generated Go and can absorb a pre-1.0 toolchain whose v1.0 target is Q1 2026 and whose release cadence is still measured in months. Do not adopt it for a production codebase that other teams must build without the `dingo` binary, because the transpiled `.go` files are the artifact everyone else has to trust. Before committing, run `dingo go` on one real package and read the output, check whether the LSP proxy survives a stale `gopls`, and confirm the Apache 2.0 claim in LICENSE matches the NOASSERTION value GitHub reports.

Official sources

  1. Issues
  2. MadAppGang/dingo on GitHub
  3. README
  4. Releases
Community notes

Community notes