Bow is a fork of the Go programming language with language and standard-library extensions aimed at clearer, more expressive code. Full design notes live in [doc/new_features/](doc/new_features/new_features.md).
Go is small, not simple. Error handling in Go is very verbose and makes code hard to read. Go is also missing tons of modern features that make code simpler — and in many cases easier to read, in our opinion.
Bow keeps Go’s strengths (fully compiled, fast compile times, goroutines / no async, simple syntax, a strong stdlib, explicit style) while greatly improving the language.
(T, error) can be represented with T!. Early return errors with the ! operator, like Rust's ? operator.
func readCount() int! {
n := parse()! // unwrap or early-return err
return n
}
func readName() string! {
return fetch()!.name // propagate error, else access .name
}
err := possibleError()
err! // early-return if err != nilFull spec: doc/new_features/result_types.md
errors.New, fmt.Errorf (with one %w), log.Fatal, and most stdlib error types capture stack traces.
err := errors.New("permission denied")
err = errors.New("open file: %s", path)
if err != nil {
return err.Wrap("load config")
}
return fmt.Errorf("readFile %s: %w", path, err)
log.Printf("%+v", err) // full chain + stacksCustom error types embed errors.Base for message, stack trace, and wrapping:
type AppError struct {
errors.Base
}
func validate(id string) error {
return errors.NewCustom[AppError]("invalid id: %s", id)
}
type NotFoundError struct {
errors.Base
Code int
}
func newNotFound(code int, msg string) *NotFoundError {
var err NotFoundError
errors.InitCustom(&err.Base, "%s", msg)
err.Code = code
return &err
}
log.Printf("%+v", newNotFound(404, "user missing"))Full spec: doc/new_features/errors.md
Optional values with ?. and ?? — distinct from T! (result / error).
var count int? = 5
label := obj?.title ?? "untitled"
n := count ?? 0
if v := lookup(3); v != nil {
use(v)
}Full spec: doc/new_features/nilable_types.md
Algebraic enums with exhaustive switching. No more flimsy iota.
enum Message {
Quit
Write { text string, bytes int }
ChangeColor { r, g, b uint8 }
}
desc := switch m {
case Quit:
"quit"
case Write { text }:
text
case ChangeColor { r, g, b }:
fmt.Sprintf("#%02x%02x%02x", r, g, b)
}Full spec: doc/new_features/enums.md
C#-style query methods on slices and iter.Seq[T].
import "linq"
nums := []int{1, 2, 3, 4, 5, 6}
firstEvenDouble := nums.Where(n => n % 2 == 0).Select(n => n * 2).First()
topThree := nums.Where(n => n > 2).OrderByDescending(n => n).Take(3).ToList()
sumOfSquares := nums.Select(n => n * n).Sum()Full spec: doc/new_features/linq.md
Single expression-only functions with inferred parameter types. No multi-line functions.
evens := nums.Where(n => n % 2 == 0)Full spec: doc/new_features/lambda_syntax.md
Methods on predeclared types, slices, generics, or types from other packages.
func (i int) Square() int { return i * i }
func (p person.Person) Hello() string {
return "Hi, " + p.Name
}
a := person.Person{Name: "Ada"}
a.Hello()Full spec: doc/new_features/extension_methods.md
Methods on generic types and methods with their own type parameters (Enhanced version of Go 1.27+ generic methods).
type List[E any] []E
func (l List[E]) Select[F any](f func(E) F) List[F] {
r := make(List[F], len(l))
for i, x := range l {
r[i] = f(x)
}
return r
}Full spec: doc/new_features/generic_methods.md
Same name, different parameter types or arity.
func connect(host string) { /* … */ }
func connect(host string, port int) { /* … */ }
connect("example.com")
connect("example.com", 8080)Full spec: doc/new_features/overloading.md
Trailing parameters may have compile-time defaults.
func log(msg string, level int = 1) {}
log("started") // level defaults to 1
log("warn", 2)Full spec: doc/new_features/default_arguments.md
User-defined operators via package-level operator methods. Good for custom data types, data science, game programming, etc.
func +(l, r *Matrix) *Matrix { /* … */ }
func ==(l, r *Matrix) bool
func !=(l, r *Matrix) bool
m := a + b
if m == other { /* … */ }Full spec: doc/new_features/operator_overloading.md
if and switch as expressions that evaluate to a value.
a := if 5 < 6 { 1 } else { 2 } // The ternary operated adapted to Go.
label := switch x {
case 1:
"one"
case 2:
"two"
default:
"other"
}Full spec: doc/new_features/expressions.md
First-class list, set, queue, stack, heaps, and trees.
nums := list.Of(1, 2, 3)
nums.Append(4)
tags := {}string{"go", "linq", "go"} // set literal
q := queue.Of("a", "b")
q.Enqueue("c")Full spec: doc/new_features/data_structures.md
Custom time formatting using .NET-style date/time formatting.
t.FormatCustom("MM/dd/yyyy g") // "06/15/2009 A.D."
t.FormatCustom("MMMM dd, yyyy") // "June 15, 2009"
parsed, _ := time.ParseCustom("yyyy-MM-dd", "2009-06-15", time.UTC)Full spec: doc/new_features/library_changes.md
Drop the leading type keyword for named struct and interface types. Shorter syntax that matches other languages.
struct Person {
Name string
Age int
}
interface Reader {
Read(p []byte) (n int, err error)
}Full spec: doc/new_features/syntax.md
Compile-time null safety for pointers. Configure the project in go.mod and override regions in source.
// go.mod
nilable_pointers enable // project default: *T vs *T?
// file.go
//go:nilable_pointers disable
func legacy(p *int) { … }
//go:nilable_pointers endFull spec: doc/new_features/nilable_pointer_types.md
Type-checker indexes to keep compile times fast with Bow features (operator overload resolution, extension methods, enums, and more).
Full spec: doc/new_features/compiler_performance.md
Language-server support for Bow syntax — completion, diagnostics, and signature help for overloads and extensions.
Full spec: doc/new_features/gopls.md
Bow changes several surprising upstream Go behaviors so bugs fail fast with stack traces instead of silent wrong behavior or hangs.
- Nil pointer receivers — calling a method on a
nilpointer panics at the call site; the method body does not run with a nil receiver. No more defensiveif c == nil { return nil }guards inside methods. - Nil channel receive — receiving from a
nilchannel panics instead of blocking forever.
var c *Connection
c.Subroute("api") // panics here — not inside Subroute
var ch chan int
<-ch // panics — does not hangUse ?. at call sites when nil is expected and the call should be skipped. modernize removes obsolete in-method nil-receiver guards and adds ?. only where the old guard returned nil or zero.
- Typed nil in interfaces —
var p *T; var i I = p→i == nilis true (upstream: false). See interface_nil_eq.md.
Full spec: doc/new_features/nil_receivers.md, doc/new_features/fixed_weird_behaviors.md, doc/new_features/weird_behaviors.md
Shorter literal syntax for slices, maps, and sets. Types are inferred from entries (untyped integers default to int).
a := ["string", "asdf"] // []string{"string", "asdf"}
m := {"a": "b"} // map[string]string{"a": "b"}
s := {"a", "b", "c"} // set.Of("a", "b", "c")
tags := {}string{"go", "linq"} // typed set literalgo fix and modernize rewrite long forms only when the inferred type matches the explicit prefix — []int{1, 2, 3} becomes [1, 2, 3], but []int64{1, 2, 3} is left unchanged.
Full spec: doc/new_features/syntax.md
for-in replaces for range for iterating slices, maps, channels, and other sequences.
for item in items {
use(item)
}
for i, item in items {
use(i, item)
}
for i, _ in items { // index-only
use(i)
}Full spec: doc/new_features/syntax.md
Prefix spread in variadic calls and literals; Python-style negative slice bounds.
myFunc(...nums) // preferred over myFunc(nums...)
more := ["fruit", ...a]
last := list[:-1] // drop last element
head := list[:5] // standard omitted bounds still validFull spec: doc/new_features/syntax.md, doc/new_features/syntax.md
Double-quoted strings interpolate expressions; backtick strings do not.
price := 12.5
msg := "Price is {price:.2f}"
escaped := "literal \\{braces\\}"
raw := `not {interpolated}`modernize converts fmt.Sprintf and string + chains where possible, and escapes literal braces in existing quoted strings.
Full spec: doc/new_features/syntax.md
See also the quick reference table in the feature index.
Unless otherwise noted, the Go source files are distributed under the BSD-style license found in the LICENSE file.
Building Bow requires a working upstream Go toolchain (Go 1.24.6 or later). Download an official binary release from Google:
Follow the install guide for your platform: https://go.dev/doc/install
Verify the bootstrap toolchain:
go versionOn Windows (PowerShell):
go versionClone or copy this repository, then follow doc/new_docs/installation.md for the full build process. A short summary is in Build from source below.
After the build, add Bow’s bin directory to your PATH and set GOROOT to the fork root.
Bow follows the upstream Installing Go from source process: a bootstrap Go compiler builds this tree, then the result becomes your new GOROOT.
Full instructions: doc/new_docs/installation.md
You need GOROOT_BOOTSTRAP pointing at upstream Go (not Bow) — typically the install from go.dev/dl. Set it explicitly if go on your PATH is missing or already points at Bow.
| Platform | Build command | Notes |
|---|---|---|
| Linux | ./make.bash |
Run from $GOROOT/src. Use ./all.bash to build and run tests. |
| macOS | ./make.bash |
Same as Linux. Do not use make.bash on Windows. |
| Windows | make.bat |
Run from %GOROOT%\src. Requires MinGW — see below. Use all.bat to build and test. |
Replace /path/to/bow/go with the absolute path to this repository’s go directory.
export GOROOT_BOOTSTRAP=$(go env GOROOT)
export GOEXPERIMENT=genericmethods # optional; generic methods
cd /path/to/bow/go/src
./make.bash # build toolchain only
# ./all.bash # build + run tests (long)
export GOROOT=/path/to/bow/go
export PATH=$GOROOT/bin:$PATH
go versionAdd the export lines to your shell profile (~/.bashrc, ~/.zshrc, etc.) to make Bow permanent.
Windows builds need a C compiler on PATH (MinGW gcc) unless you set CGO_ENABLED=0. Upstream documents the full setup on the Go Wiki: WindowsBuild page; see also doc/new_docs/installation.md.
Install MinGW (summary):
- Download the MinGW installer from SourceForge — mingw-get-inst (WindowsBuild).
- Enable C Compiler, MinGW Developer Toolkit, and MSYS Basic System.
- Add
C:\MinGW\bin(and MSYS bin if needed) toPATH. - Verify:
gcc --version.
Install upstream Go from go.dev/dl, then:
$env:GOROOT_BOOTSTRAP = (go env GOROOT)
$env:GOEXPERIMENT = "genericmethods" # optional
cd C:\path\to\bow\go\src
.\make.bat # build toolchain only
# .\all.bat # build + run tests (long)
$env:GOROOT = "C:\path\to\bow\go"
$env:PATH = "$env:GOROOT\bin;$env:PATH"
go versionAdd GOROOT and update PATH in System Environment Variables to keep Bow across sessions.
GOROOT_BOOTSTRAPmust containbin/go(orbin\go.exeon Windows) from upstream Go ≥ 1.24.6.- If unset, the scripts search common locations (
$HOME/go1.24.6,$HOME/sdk/go1.24.6, etc.) and any othergoonPATHwhoseGOROOTis not Bow. make.bash/make.batonly build the toolchain.all.bash/all.batalso run the full test suite.- For IDE support, build gopls from the
go_toolsrepository against Bow’sGOROOT.
Go is the work of thousands of contributors. We appreciate your help!
To contribute upstream, read https://go.dev/doc/contribute. For Bow, see doc/new_features/ for the feature index and design docs.
Bow adds new syntax and stdlib features. To move an existing codebase over:
-
Install Bow and set
GOROOTandPATH— see Build from source above or doc/new_docs/installation.md. -
Clone and build modernize with Bow as
GOROOT, then run it on your module. Modernize applies mechanical rewrites for nil-receiver guard removal, nilable pointers,T!/!error handling, structured errors, for-in loops, shorthand literals, interpolated strings, spread calls, negative slice indices, and struct/interface shorthand. See the modernize README for usage. -
Use an AI assistant (Cursor, Claude, etc.). Clone SyntaxExample. Point AI at
doc/new_features/and SyntaxExample for features modernize does not cover — LINQ, enums, extension methods, and the rest documented there.