Dingo
Go that escaped.
Features • Quick Start • Examples • Status • Roadmap • Contributing
At a Glance
Sum Types: Working | Pattern Matching: Working | Error Propagation: Working | Functional Utils: Working | Playground for Go’s Future | v1.0 Target: Late 2025
Look, I love Go. But...
You know that feeling when you’re writing Go and you type if err != nil for the 47th time in a single file?
Or when you forget to check for nil and your production server learns what a panic feels like?
Or when you’re explaining to a Rust developer why Go doesn’t have sum types and they look…
Dingo
Go that escaped.
Features • Quick Start • Examples • Status • Roadmap • Contributing
At a Glance
Sum Types: Working | Pattern Matching: Working | Error Propagation: Working | Functional Utils: Working | Playground for Go’s Future | v1.0 Target: Late 2025
Look, I love Go. But...
You know that feeling when you’re writing Go and you type if err != nil for the 47th time in a single file?
Or when you forget to check for nil and your production server learns what a panic feels like?
Or when you’re explaining to a Rust developer why Go doesn’t have sum types and they look at you like you just said “we don’t believe in seatbelts”?
Yeah. That’s why Dingo exists.
What’s Dingo?
Think TypeScript, but for Go.
Dingo is a language that compiles to clean, idiomatic Go code. Not some franken-runtime or a whole new ecosystem. Just better syntax that becomes regular Go.
The pitch: Write code with Result types, pattern matching, and null safety. Get back perfect Go code that your team can read, your tools can process, and your production servers can run at exactly the same speed.
Zero runtime overhead. Zero new dependencies. Zero “what’s this weird thing in my transpiled code?”
Is this proven to work? Yes. Borgo (4.5k stars) already proved you can transpile to Go successfully. Dingo builds on that foundation with better IDE integration, source maps, and a pure Go implementation.
Why “Dingo”?
Ever wonder what a dingo actually is?
Thousands of years ago, they were domesticated dogs. Well-behaved. Following commands. Controlled.
Then they escaped to the Australian wild and evolved into something science couldn’t categorize. Not quite dog. Not quite wolf. Ungovernable.
The Go Gopher? Created at Google. Lives by the rules. Does what it’s told.
Dingo broke free.
Here’s the beautiful part: dingos are still canines. They didn’t reject their DNA—they just refused to be controlled. Same with our language.
Every Go feature still works. Go 1.24 adds something? You get it in Dingo. Day one. Disable all plugins? You’re running pure Go.
You’re not losing anything. You’re gaining freedom without asking permission.
Want pattern matching? Enable it. Want sum types? Already working. Think you can do it better? Fork the plugin and prove it.
Your language. Your rules. No committee required.
See MANIFESTO.md for why this terrifies the establishment.
Quick Start
Note: Dingo is in active development. Phase V Complete - Infrastructure ready for v1.0 with comprehensive documentation, workspace builds, CI/CD enhancements, and 3/4 external model approval.
Latest (2025-11-22): ✅ LSP Integration Complete - Post-AST source maps (100% accurate position mapping) + auto-rebuild on save. Edit → Save → IDE features work instantly! Hover, go-to-definition, and automatic transpilation fully functional. See CHANGELOG.md for details.
Installation
# Clone the repository
git clone https://github.com/MadAppGang/dingo.git
cd dingo
# Build the compiler
go build -o dingo ./cmd/dingo
# Add to PATH (optional)
export PATH=$PATH:$(pwd)
Your First Dingo Program
Create hello.dingo:
package main
import "fmt"
func main() {
let message = "Hello from Dingo!"
fmt.Println(message)
}
Build and run:
# Transpile to Go
dingo build hello.dingo
# Or compile and run in one step
dingo run hello.dingo
Try Working Features Now
Sum Types with Pattern Matching:
enum Result {
Ok(value: int),
Error(message: string)
}
func divide(a: int, b: int) Result {
if b == 0 {
return Error("division by zero")
}
return Ok(a / b)
}
let result = divide(10, 2)
match result {
Ok(value) => fmt.Printf("Success: %d\n", value),
Error(msg) => fmt.Printf("Error: %s\n", msg)
}
Safe Navigation and Null Coalescing (Phase 7 ✅):
// Property access with safe navigation
let city = user?.address?.city?.name ?? "Unknown"
// Method calls with safe navigation
let email = user?.getProfile()?.email ?? "noreply@example.com"
// Works with Go pointers too!
let timeout = config?.database?.timeout ?? 30
// Chained defaults
let theme = user?.theme ?? project?.theme ?? global?.theme ?? "light"
Functional Utilities:
let numbers = []int{1, 2, 3, 4, 5}
let doubled = numbers.map(func(x int) int { return x * 2 })
let evens = numbers.filter(func(x int) bool { return x % 2 == 0 })
let sum = numbers.reduce(0, func(acc int, x int) int { return acc + x })
See examples/ and docs/features/ for more working code.
Why Dingo?
### The Go Pain Points+ 47 if err != nil blocks per file+ Nil pointer panics in production + No sum types after 15 years of requests + Verbose error handling drowning business logic + No null safety operators + Boilerplate enums requiring manual type guards | ### The Dingo Solution+ ? operator propagates errors cleanly+ Option<T> makes nil checks compile-time safe+ enum keyword with full sum type support+ Pattern matching with exhaustiveness checking + ?. and ?? for safe navigation (coming soon)+ Zero overhead - transpiles to clean Go |
Key Insight: Dingo doesn’t change Go. It transpiles to it. Your team gets modern ergonomics, your production gets pure Go performance.
The Hidden Superpower: Use Dingo Selfishly, Help Go Evolve Naturally
Here’s what makes Dingo special — you get two massive benefits simultaneously:
1. Revolutionize YOUR Codebase TODAY
This is why you’ll actually use Dingo:
- ✅ 67% less error handling boilerplate —
?operator instead of 47if err != nilblocks - ✅ 78% code reduction with sum types — Rust-style enums that just work
- ✅ Zero nil pointer panics — Option types that the compiler enforces
- ✅ Pattern matching — Exhaustive, type-safe, impossible to mess up
- ✅ Same performance — Transpiles to clean Go, zero runtime overhead
Your code becomes cleaner, safer, and more maintainable. Immediately.
This is the selfish reason to use Dingo. And it’s a damn good reason.
2. Shape Go’s Future (As a Natural Side Effect)
Here’s the beautiful part you get for free:
While you’re using Dingo to make YOUR code better, you’re automatically contributing to Go’s evolution:
- 📊 Your metrics become data — “67% reduction in error handling code across 50 real projects”
- 🐛 Your bugs find edge cases — Real problems that theoretical debates miss
- ✨ Your usage validates ideas — Proof that features work in production
- 📚 Your code becomes examples — Concrete demonstrations for Go proposals
You don’t have to think about this. It just happens.
The TypeScript Parallel (This Is EXACTLY What Happened)
Developers didn’t adopt TypeScript to “help JavaScript evolve.”
They adopted it because it made their codebases better.
- TypeScript added types → Developers used them → JavaScript saw it worked → JavaScript added types
- TypeScript added async/await → Millions used it → JavaScript saw the value → JavaScript adopted it
- TypeScript added optional chaining → Everyone loved it → JavaScript added it officially
Developers used TypeScript selfishly. JavaScript evolved as a natural consequence.
Same thing is happening with Dingo and Go:
- You use Dingo because it makes error handling less painful
- 50,000 other developers do the same thing
- Go team sees 2 years of production data showing it works
- Go proposal now has concrete evidence instead of theoretical debate
- Everyone wins
You’re not doing charity work. You’re writing better code. Helping Go evolve is just a happy side effect.
Why This Matters More Than You Think
For decades, programming language evolution has been broken:
❌ The old way: Community → Proposal → Years of debate → Maybe no → Frustration
✅ The Dingo way: Developers → Use features → Data emerges → Go team decides with evidence
This is how TypeScript revolutionized JavaScript. Not through proposals, but through proving ideas in production.
The Win-Win-Win Scenario
🎯 You win: Better code today, zero waiting 🎯 Go team wins: Real data for decisions, reduced risk 🎯 Go ecosystem wins: Faster evolution, battle-tested features
Example: Sum Types
Imagine if before Go Proposal #19412 (sum types - 996+ 👍 but rejected), there was:
- ✅ 50,000 developers using it for 2 years
- ✅ Concrete metrics: 78% code reduction
- ✅ Known edge cases documented
- ✅ Production validation across 5,000+ projects
The proposal would have been impossible to reject with that evidence.
That’s what Dingo enables. Every feature you use contributes data that could reshape Go’s future.
This Is Not Hypothetical — TypeScript Proved It Works
Here’s what actually happened with TypeScript and JavaScript:
| Feature | TypeScript Added | Developers Used It | JavaScript Adopted | Timeline |
|---|---|---|---|---|
| Async/Await | 2015 | Millions of codebases | ES2017 | 2 years |
| Optional Chaining | 2019 | Massive adoption | ES2020 | 1 year |
| Nullish Coalescing | 2019 | Widespread use | ES2020 | 1 year |
| Class Fields | 2017 | Standard in TS code | ES2022 | 5 years |
| Decorators | 2015 | Widely used | Stage 3 proposal | Still evolving |
Notice the pattern:
- TypeScript adds feature
- Developers use it (for selfish reasons - better code)
- Real-world data proves it works
- JavaScript adopts it with evidence-based confidence
Dingo enables the exact same cycle for Go.
You’re not choosing between “make my code better” OR “help Go evolve.”
You get both. Automatically. Simultaneously.
Why should you care?
Here’s what the Go community has been begging for since 2009:
| What developers want | How badly | What Dingo gives you |
|---|---|---|
Stop typing if err != nil every 3 lines | ⭐⭐⭐⭐⭐ | Result<T, E> + the ? operator |
| Stop shipping nil pointer panics | ⭐⭐⭐⭐⭐ | Option<T> type that the compiler actually checks |
| Sum types (seriously, it’s been 15 years) | ⭐⭐⭐⭐⭐ 996+ 👍 | enum with pattern matching |
| Enums that can’t be invalid | ⭐⭐⭐⭐⭐ 900+ 👍 | Type-safe enums with exhaustiveness |
| Lambda functions that don’t take 4 lines | ⭐⭐⭐⭐ 750+ 👍 | ` |
The Go team has valid reasons for rejecting these features. They’re not wrong about simplicity.
But here’s the thing: Dingo doesn’t change Go. We just compile to it.
Want sum types? Great. They become tagged structs in Go. Want the ? operator? Cool. It becomes if err != nil checks. Want pattern matching? Done. It’s a switch statement underneath.
Your Go code stays pure. Your Dingo code stays sane.
Show Me Code or I’m Leaving
Fair enough.
Code Reduction in Action
| Metric | Traditional Go | With Dingo | Savings |
|---|---|---|---|
| Sum Type Definition | 33 lines | 7 lines | 79% less code |
| Enum with Data | 46 lines | 10 lines | 78% less code |
| Error Handling Pipeline | 85 lines | 28 lines | 67% less code |
| API Handler | 42 lines | 15 lines | 64% less code |
All numbers from real examples in our test suite
The “if err != nil” problem
What you write in Go today:
func processOrder(orderID string) (*Order, error) {
order, err := fetchOrder(orderID)
if err != nil {
return nil, fmt.Errorf("fetch failed: %w", err)
}
validated, err := validateOrder(order)
if err != nil {
return nil, fmt.Errorf("validation failed: %w", err)
}
payment, err := processPayment(validated)
if err != nil {
return nil, fmt.Errorf("payment failed: %w", err)
}
return payment, nil
}
75% of this function is error handling ceremony. The actual logic is hiding somewhere in there.
What you write in Dingo:
func processOrder(orderID: string) -> Result<Order, Error> {
let order = fetchOrder(orderID)?
let validated = validateOrder(order)?
let payment = processPayment(validated)?
return Ok(payment)
}
Same safety. Same error handling. 60% less code.
The ? just means “if this is an error, return it. Otherwise, unwrap the value and keep going.” That’s it. That’s the entire feature.
Rust developers have been using this for 8 years. They love it so much they put it on t-shirts.
Real Working Examples (From Our Test Suite)
These are actual examples from Dingo’s test suite that transpile and run today.
Example 1: Simple Sum Type
| What You Write (Dingo) |
package main
enum Status {
Pending,
Active,
Complete,
}
``` | **What You Get \(Generated Go\)**
package main
type StatusTag uint8
const ( StatusTagPending StatusTag = iota StatusTagActive StatusTagComplete )
type Status struct { tag StatusTag }
func Status_Pending() Status { return Status{tag: StatusTagPending} }
func Status_Active() Status { return Status{tag: StatusTagActive} }
func Status_Complete() Status { return Status{tag: StatusTagComplete} }
func (e Status) IsPending() bool { return e.tag == StatusTagPending }
func (e Status) IsActive() bool { return e.tag == StatusTagActive }
func (e Status) IsComplete() bool { return e.tag == StatusTagComplete }
**7 lines of Dingo → 33 lines of idiomatic, type-safe Go\.** All the boilerplate written for you\.
---
### Example 2: Sum Type with Associated Data
| | |
| - | - |
| **What You Write \(Dingo\)**
package main
enum Shape { Point, Circle { radius: float64 }, Rectangle { width: float64, height: float64 }, }
package main
type ShapeTag uint8
const ( ShapeTagPoint ShapeTag = iota ShapeTagCircle ShapeTagRectangle )
type Shape struct { tag ShapeTag circleRadius *float64 rectangleWidth *float64 rectangleHeight *float64 }
func Shape_Point() Shape { return Shape{tag: ShapeTagPoint} }
func Shape_Circle(radius float64) Shape { return Shape{ tag: ShapeTagCircle, circleRadius: &radius, } }
func Shape_Rectangle(width, height float64) Shape { return Shape{ tag: ShapeTagRectangle, rectangleWidth: &width, rectangleHeight: &height, } }
func (e Shape) IsPoint() bool { return e.tag == ShapeTagPoint }
func (e Shape) IsCircle() bool { return e.tag == ShapeTagCircle }
func (e Shape) IsRectangle() bool { return e.tag == ShapeTagRectangle }
**10 lines of Dingo → 46 lines of type-safe Go\.** Rust-style enums without the Rust learning curve\.
---
### Example 3: Basic Syntax Sugar
| | |
| - | - |
| **What You Write \(Dingo\)**
package main
func main() { let message = “Hello, Dingo!” println(message) return }
func add(a: int, b: int) int { return a + b }
package main
func main() { var message = “Hello, Dingo!” println(message) return }
func add(a int, b int) int { return a + b }
**Clean parameter syntax with `:` separators\.** Transpiles to standard Go that any gopher can read\.
---
## The "Holy Crap" Example
Want to see something beautiful? Here's real-world Go code I found in production:
**Go \(85 lines of pain\):**
func ProcessUserDataPipeline(userID string, options *ProcessOptions) (*UserReport, error) { // Fetch user user, err := db.GetUser(userID) if err != nil { return nil, fmt.Errorf(“failed to get user: %w”, err) } if user == nil { return nil, errors.New(“user not found”) }
// Get user’s orders orders, err := db.GetOrdersForUser(user.ID) if err != nil { return nil, fmt.Errorf(“failed to get orders: %w”, err) }
// Filter valid orders var validOrders []Order for _, order := range orders { if order.Status != “cancelled” && order.Total > 0 { validOrders = append(validOrders, order) } }
// Calculate totals var totalSpent float64 for _, order := range validOrders { totalSpent += order.Total }
// Get user preferences prefs, err := db.GetPreferences(user.ID) if err != nil { return nil, fmt.Errorf(“failed to get preferences: %w”, err) }
// Apply discount if premium discount := 0.0 if prefs != nil && prefs.IsPremium { discount = totalSpent * 0.1 }
// Get shipping address address, err := db.GetShippingAddress(user.ID) if err != nil { return nil, fmt.Errorf(“failed to get address: %w”, err) }
// Format city name cityName := “Unknown” if address != nil && address.City != nil { cityName = *address.City }
// Get payment methods payments, err := db.GetPaymentMethods(user.ID) if err != nil { return nil, fmt.Errorf(“failed to get payment methods: %w”, err) }
// Find default payment var defaultPayment *PaymentMethod for i := range payments { if payments[i].IsDefault { defaultPayment = &payments[i] break } }
// Get recommendation score score, err := analytics.GetRecommendationScore(user.ID) if err != nil { // Non-critical, use default score = 0.0 }
// Build report report := &UserReport{ UserID: user.ID, Email: user.Email, TotalSpent: totalSpent, Discount: discount, OrderCount: len(validOrders), City: cityName, HasPayment: defaultPayment != nil, RecommendScore: score, }
return report, nil }
**Dingo \(28 lines of clarity\):**
func ProcessUserDataPipeline(userID: string, options: ProcessOptions) -> Result<UserReport, Error> { let user = db.GetUser(userID)?.okOr(“user not found”)?
let orders = db.GetOrdersForUser(user.ID)? let validOrders = orders.filter(|o| o.status != “cancelled” && o.total > 0) let totalSpent = validOrders.map(|o| o.total).sum()
let prefs = db.GetPreferences(user.ID)? let discount = prefs.isPremium ? totalSpent * 0.1 : 0.0
let address = db.GetShippingAddress(user.ID)? let cityName = address?.city?.name ?? “Unknown”
let payments = db.GetPaymentMethods(user.ID)? let defaultPayment = payments.find(|p| p.isDefault)
let score = analytics.GetRecommendationScore(user.ID).unwrapOr(0.0)
return Ok(UserReport{ userID: user.id, email: user.email, totalSpent: totalSpent, discount: discount, orderCount: validOrders.len(), city: cityName, hasPayment: defaultPayment.isSome(), recommendScore: score, }) }
**67% less code\. Same functionality\. Infinitely more readable\.**
Look at what just happened:
- ✅ Error propagation with `?` eliminated 12 `if err != nil` blocks
- ✅ Lambda functions turned 8-line loops into one-liners
- ✅ Optional chaining `?.` replaced nested nil checks
- ✅ Ternary operator cleaned up conditional assignments
- ✅ `.filter()`, `.map()`, `.sum()` made collection operations obvious
The business logic literally jumps off the screen now\. You can see what it's doing instead of drowning in error handling boilerplate\.
This is what Dingo does\. It takes your Go code and makes it *readable*\.
---
## Features That Make Dingo Special
| Feature | Status | Description |
| ------- | ------ | ----------- |
| **Sum Types** | ✅ Working | Rust-style enums with associated data |
| **Pattern Matching** | ✅ Working | Exhaustive match expressions with destructuring |
| **Error Propagation** | ✅ Working | The `?` operator for clean error handling |
| **Functional Utilities** | ✅ Working | `map`, `filter`, `reduce` with zero overhead |
| **Result & Option** | ✅ Infrastructure Ready | Type-safe error and null handling \(integration pending\) |
| **Safe Navigation** | ✅ Complete \(Phase 7\) | `?.` operator for properties and methods |
| **Null Coalescing** | ✅ Complete \(Phase 7\) | `??` operator for default values |
| **Lambda Syntax** | ✅ Complete \(Phase 6\) | TypeScript arrows and Rust pipes \(configurable\) |
| **Language Server** | ✅ Working \(Phase 10\) | Full IDE support via gopls proxy with source maps |
---
## Feature Deep Dive
### 1\. Result Type — Error handling for grown-ups
Stop returning `(value, error)` tuples and hoping callers remember to check both\.
func fetchUser(id: string) -> Result<User, DatabaseError> { if !isValidID(id) { return Err(DatabaseError.invalidID(id)) }
let user = database.query(id) return Ok(user) }
// The compiler forces you to handle both cases match fetchUser(“123”) { Ok(user) => println(“Found: ${user.name}”), Err(error) => println(“Database said no: ${error.message}”) }
Your function signature now tells you exactly what can go wrong\. No surprises\. No "oh, I didn't know this could return an error\."
### 2\. Error Propagation \(`?`\) — Because life's too short
This one's from Rust, and it's honestly genius\.
func getUserProfile(userID: string) -> Result<Profile, Error> { let user = fetchUser(userID)? // Returns error if this fails let posts = fetchPosts(user.ID)? // Or this let comments = fetchComments(user.ID)? // Or this
return Ok(Profile{user, posts, comments}) }
Every `?` is an escape hatch\. Hit an error? Jump straight to the return\. Got a value? Keep going\.
It's like `try/catch` but without the invisible control flow that makes you question your life choices\.
### 3\. Option Type — Nil pointers are a billion-dollar mistake
Tony Hoare \(the guy who invented null references\) literally apologized for it\. Called it his "billion-dollar mistake\."
We don't have to keep living with that mistake\.
func findUser(email: string) -> Option
// Safe navigation like it’s 2024 let city = user?.address?.city?.name ?? “Unknown”
// Compiler won’t let you forget to check match findUser(“test@example.com”) { Some(user) => sendEmail(user), None => println(“Who dis?”) }
Can't accidentally dereference `None`\. The type system literally won't let you compile if you forget to handle it\.
How many production panics would this have prevented in your codebase? I'll wait\.
### 4\. Pattern Matching — Switch statements grew up
Go's switch is fine\. But imagine if it could do *this*:
enum HttpResponse { Ok(body: string), NotFound, ServerError{code: int, message: string}, Redirect(url: string) }
func handleResponse(resp: HttpResponse) -> string { match resp { Ok(body) => “Success: ${body}”, NotFound => “404: Not found”, ServerError{code, message} => “Error ${code}: ${message}”, Redirect(url) => “Redirecting to ${url}” // Forget a case? Compiler yells at you } }
Exhaustiveness checking means the compiler knows every possible case\. Miss one? Won't compile\.
No more "oh crap, we didn't handle the timeout case" at 2 AM\.
### 5\. Sum Types — The \#1 requested feature in Go history
996 upvotes on the Go proposal\. That's not just popular, that's "the entire community is screaming for this\."
enum State { Idle, Loading{progress: float}, Success{data: string}, Error{message: string} }
func render(state: State) -> string { match state { Idle => “Ready when you are”, Loading{progress} if progress < 50 => “Just getting started…”, Loading{progress} => “Loading: ${progress}%”, Success{data} => showSuccess(data), Error{message} => showError(message) } }
A value can be one of several types\. The compiler tracks which one\. You can't mess it up\.
This is how Rust does enums\. How Swift does enums\. How Kotlin does sealed classes\. How TypeScript does discriminated unions\.
Everyone has this except Go\. Until now\.
### 6\. Lambda Functions — Choose your style \(TypeScript or Rust\)
Dingo supports **two lambda syntax styles** \(configurable in `dingo.toml`\), giving you the conciseness of modern languages without the verbosity of Go's function literals\.
**TypeScript/JavaScript arrow functions \(default\):**
// Single parameter (no parens needed) users.filter(u => u.age > 18) .map(u => u.name) .sorted()
// Multiple parameters (parens required) numbers.reduce((acc, x) => acc + x)
// With explicit types when needed let parser = (s: string): int => parseInt(s)
**Rust style with pipes:**
// Single or multiple parameters users.filter(|u| u.age > 18) .map(|u| u.name) .sorted()
numbers.reduce(|acc, x| acc + x)
// With explicit types when needed let parser = |s: string| -> int { parseInt(s) }
**Configuration** \(`dingo.toml`\):
[syntax] lambda_style = “typescript” # or “rust”
**Type inference:** Dingo uses go/types to infer parameter types from context\. When inference fails, just add explicit type annotations:
// ✅ Type inferred from filter signature users.filter(u => u.age > 18)
// ❌ No context - inference fails let standalone = x => x * 2
// ✅ Fix with explicit type let standalone = (x: int) => x * 2 // TypeScript style let standalone = |x: int| x * 2 // Rust style
Compare that to Go's verbose function literals:
filteredUsers := make([]User, 0) for _, u := range users { if u.Age > 18 { filteredUsers = append(filteredUsers, u) } }
names := make([]string, 0, len(filteredUsers)) for _, u := range filteredUsers { names = append(names, u.Name) }
sort.Strings(names)
**60-70% code reduction** for simple callbacks\. The business logic stands out instead of being buried in ceremony\.
**Why two styles?**
- **TypeScript arrows**: Largest developer community familiarity \(JavaScript/TypeScript devs\)
- **Rust pipes**: Clear, explicit, familiar to Rust developers
- **Configuration-driven**: Pick one per project, no confusion
**Why no currying?** Basic lambdas solve 95%\+ of real use cases\. Currying \(`|x| |y| x + y`\) is rare even in Rust \(10-15% usage\), doesn't fit Go's pragmatic culture, and adds complexity for minimal benefit\. See `features/lambdas.md` for details\.
### 7\. Null Safety Operators — Chain nil checks like a human
**The Go way:**
var city string if user != nil && user.Address != nil && user.Address.City != nil { city = *user.Address.City } else { city = “Unknown” }
**The Dingo way:**
let city = user?.address?.city?.name ?? “Unknown”
One line\. Same safety\. Your eyes will thank you\.
**What's working now:**
- ✅ Safe navigation \(`?.`\) for properties: `user?.name`
- ✅ Safe navigation for methods: `user?.getName()`
- ✅ Method arguments: `user?.process(arg1, arg2)`
- ✅ Chaining: `user?.getProfile()?.email`
- ✅ Dual type support: Option AND Go pointers \(\*T\)
- ✅ Null coalescing \(`??`\): `value ?? default`
- ✅ Chained defaults: `a ?? b ?? c`
- ✅ Integration: `user?.name ?? "Guest"`
**See [docs/features/safe-navigation\.md](https://github.com/MadAppGang/dingo/blob/main/docs/features/safe-navigation.md) and [docs/features/null-coalescing\.md](https://github.com/MadAppGang/dingo/blob/main/docs/features/null-coalescing.md) for complete documentation\.**
### 8\. Ternary Operator — Yes, we're going there
Go rejected this\. We're adding it anyway\.
let max = a > b ? a : b let status = isActive ? “online” : “offline” println(“You have ${count} item${count == 1 ? “” : “s”}“)
Every\. Single\. Modern\. Language\. Has\. This\.
C has it\. Java has it\. JavaScript has it\. Python has it \(kinda\)\. Swift has it\. Even PHP has it\.
Go's reason for not having it? "We only need one conditional construct\."
Cool\. You do you, Go\. We'll be over here with our one-liners\.
---
## More Real-World Examples
### API Handler: Before and After
**Go \(42 lines\):**
func HandleUserUpdate(w http.ResponseWriter, r *http.Request) { userID := r.URL.Query().Get(“id”) if userID == “” { http.Error(w, “missing user ID”, http.StatusBadRequest) return }
var updateReq UpdateRequest if err := json.NewDecoder(r.Body).Decode(&updateReq); err != nil { http.Error(w, “invalid JSON”, http.StatusBadRequest) return }
user, err := db.GetUser(userID) if err != nil { http.Error(w, “database error”, http.StatusInternalServerError) return } if user == nil { http.Error(w, “user not found”, http.StatusNotFound) return }
if err := validateUpdate(&updateReq); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return }
if err := db.UpdateUser(user.ID, &updateReq); err != nil { http.Error(w, “update failed”, http.StatusInternalServerError) return }
updated, err := db.GetUser(user.ID) if err != nil { http.Error(w, “failed to fetch updated user”, http.StatusInternalServerError) return }
json.NewEncoder(w).Encode(updated) }
**Dingo \(15 lines\):**
func HandleUserUpdate(w: http.ResponseWriter, r: http.Request) { let result = processUpdate(r)
match result { Ok(user) => json.NewEncoder(w).Encode(user), Err(ApiError.BadRequest(msg)) => http.Error(w, msg, 400), Err(ApiError.NotFound(msg)) => http.Error(w, msg, 404), Err(ApiError.Internal(msg)) => http.Error(w, msg, 500), } }
func processUpdate(r: http.Request) -> Result<User, ApiError> {
let userID = r.URL.Query().Get(“id”).filter(|s| !s.isEmpty()).okOr(ApiError.BadRequest(“missing user ID”))?
let updateReq = json.NewDecoder(r.Body).Decode::
validateUpdate(updateReq)? db.UpdateUser(user.id, updateReq)? db.GetUser(user.id) }
### Data Processing Pipeline
**Go \(complex nested loops and error handling\):**
func ProcessDataBatch(items []Item) ([]ProcessedItem, error) { var processed []ProcessedItem
for _, item := range items { if item.IsValid() { enriched, err := enrichItem(item) if err != nil { log.Printf(“failed to enrich item %s: %v”, item.ID, err) continue }
validated, err := validateItem(enriched) if err != nil { log.Printf(“validation failed for item %s: %v”, item.ID, err) continue }
transformed, err := transformItem(validated) if err != nil { return nil, fmt.Errorf(“transform failed: %w”, err) }
processed = append(processed, transformed) } }
if len(processed) == 0 { return nil, errors.New(“no items processed”) }
return processed, nil }
**Dingo \(functional pipeline\):**
func ProcessDataBatch(items: []Item) -> Result<[]ProcessedItem, Error> { let processed = items .filter { it.isValid() } .mapWithLog { enrichItem(it) } .mapWithLog { validateItem(it) } .map { transformItem(it) } .collect()?
- processed.isEmpty() ? Err(Error.new(“no items processed”))
- Ok(processed) }
The functional style makes the data flow obvious: filter → enrich → validate → transform → collect\.
---
## How does this actually work?
Two-part system, just like TypeScript:
### The Transpiler \(`dingo build`\) - Two-Stage Architecture
**Stage 1: Preprocessor** \(Text-based transformations\)
- Converts Dingo-specific syntax to valid Go using regex-based pattern matching
- `TypeAnnotProcessor`: Transforms `param: Type` → `param Type`
- `ErrorPropProcessor`: Expands `x?` → proper error handling code
- `EnumProcessor`: Converts `enum Name { Variant }` → Go tagged union structs
- `KeywordProcessor`: Handles other Dingo keywords
- Output: Valid Go code \(no Dingo syntax remains\)
**Stage 2: AST Processing** \(Structural transformations\)
- Uses native `go/parser` to parse the preprocessed Go code
- Plugin pipeline \(Discovery → Transform → Inject\) modifies the AST
- Result type plugin transforms `Ok()/Err()` constructors
- Generates clean `.go` files and source maps using `go/printer`
**Why this approach?**
- Preprocessors handle syntax that go/parser can't understand \(`enum`, `:`, etc\.\)
- Then go/parser does the heavy lifting \(no custom parser needed\!\)
- Plugins add semantic transformations on valid Go AST
- Simple, maintainable, leverages Go's own tooling
The generated Go code looks like what you'd write by hand\. Not some machine-generated nightmare\.
### The Language Server \(`dingo-lsp`\) - ✅ Working
Wraps gopls \(Go's language server\)\. Intercepts LSP requests\. Translates positions using source maps\. Forwards to gopls\.
You get autocomplete, go-to-definition, diagnostics, hover info — everything gopls does\. Working in VS Code right now\.
Your editor thinks it's editing Go\. Your terminal thinks it's compiling Go\. Only you know you're actually writing Dingo\.
---
## What the generated code looks like
Let's see what actually comes out of the transpiler\.
**You write this Dingo:**
func findUser(email: string) -> Option
match findUser(“test@example.com”) { Some(user) => sendEmail(user), None => println(“Not found”) }
**Dingo generates this Go:**
type OptionUser struct { value *User isSet bool }
func findUser(email string) OptionUser { users := db.query(“SELECT * FROM users WHERE email = ?”, email) if users.isEmpty() { return OptionUser{isSet: false} } return OptionUser{value: &users[0], isSet: true} }
opt := findUser(“test@example.com”) if opt.isSet { user := *opt.value sendEmail(user) } else { fmt.Println(“Not found”) }
Clean\. Readable\. Exactly what you'd write if you were implementing Option types by hand in Go\.
No magic\. No runtime library\. Just structs and if statements\.
---
## Your questions, answered
### Is this ready to use right now?
**Partially\.** We're in active development with several features already working:
**Working Today:**
- Sum types with `enum` keyword
- Pattern matching with `match` expressions
- Error propagation with `?` operator
- Functional utilities \(`map`, `filter`, `reduce`, etc\.\)
- Beautiful CLI tooling \(`dingo build`, `dingo run`\)
- Clean, idiomatic code generation
**Coming Soon:**
- `Result<T, E>` and `Option<T>` integration
- Lambda syntax sugar
- Safe navigation operators
- Language server and IDE support
**Ready to experiment?** Clone the repo, build the compiler, and try it out\. The transpiler works, generates clean Go code, and Phase 2\.7 is complete\.
**Ready for production?** Not yet\. Wait for v1\.0 \(10-12 months\)\.
Want to follow along? Star the repo and watch the releases\.
### Will my existing Go code work with this?
Yes\. 100%\.
Import any Go package into Dingo\. Call any Go function\. Use any Go library\. It all just works\.
You can even mix `.go` and `.dingo` files in the same project\. Migrate gradually\. No big-bang rewrite required\.
### What about performance?
Dingo compiles to Go\. Go compiles to machine code\. Same machine code\.
Zero runtime overhead\. No wrapper library\. No reflection tricks\. No performance penalty\.
If your Go code runs fast, your Dingo code runs at exactly the same speed\. Because it *is* Go code\.
### Why not just wait for Go to add these features?
The Go team has been saying no to sum types for 15 years\. They're not changing their minds\.
And honestly? They're probably right *for Go*\. These features add complexity\. Go values simplicity\.
But Dingo isn't Go\. We're a meta-language\. We can add features that transpile to simple Go underneath\.
Best of both worlds\.
### How is this different from Borgo?
**First: Borgo deserves massive respect\.**
Borgo \([github\.com/borgo-lang/borgo](https://github.com/borgo-lang/borgo)\) proved this entire concept works\. 4\.5k stars\. Real production users\. They showed the world that transpiling to Go is viable\.
**What Borgo proved:**
- ✅ Transpiling modern syntax to Go actually works
- ✅ Result/Option/pattern matching can run on Go's runtime
- ✅ There's massive demand for Go with better ergonomics
- ✅ Zero runtime overhead is achievable
- ✅ You can have sum types without changing Go itself
Borgo taught us WHAT to build\. Now Dingo is building it BETTER\.
**Why Dingo is taking a different path:**
| Aspect | Borgo | Dingo | Why It Matters |
| ------ | ----- | ----- | -------------- |
| **Syntax** | Rust-like | Go-like \+ enhancements | Go developers don't need to learn Rust |
| **Implementation** | Written in Rust | Pure Go | No Rust toolchain required |
| **Architecture** | Monolithic transpiler | Plugin-based system | Extensible, community can add features |
| **IDE Support** | Basic/none | Full gopls proxy via LSP | Autocomplete, refactoring, diagnostics |
| **Source Maps** | No | Yes | Error messages point to \.dingo files, not generated Go |
| **Target Audience** | Rust devs wanting Go runtime | Go devs wanting modern features | Different user base |
| **Maintenance** | Last commit: 2023 | Active development | Community support and updates |
| **Lambda Syntax** | Rust closures only | 4 styles \(Rust/TS/Kotlin/Swift\) | Pick what feels natural |
| **Error Messages** | Point to generated Go | Point to original Dingo | Debugging actually works |
**The honest truth:**
Borgo is for Rust developers who need Go's deployment/runtime but don't want to give up Rust's syntax\.
Dingo is for Go developers who love Go but are tired of typing `if err != nil` 47 times per file\.
Different problems\. Different solutions\. Both valid\.
**What we're learning from Borgo:**
- Their transpilation patterns for sum types \(brilliant\)
- How they handle pattern matching \(rock solid\)
- Their approach to zero-cost abstractions \(perfect\)
**What we're doing differently:**
- Building in Go so Go developers can contribute
- Adding gopls integration so your IDE actually works
- Supporting multiple lambda styles \(not everyone loves Rust syntax\)
- Active maintenance and community building
- Plugin architecture for extensibility
**Use Borgo if:** You're a Rust developer stuck using Go for deployment/company reasons\.
**Use Dingo if:** You're a Go developer who wants Result types without learning Rust\.
Both projects make Go better\. That's a win for everyone\.
---
## Roadmap \(the realistic version\)
### Phase 1: Core Transpiler — 8-10 weeks
- ✅ Research complete
- 🔨 Building sum types right now
- 🔜 Result and Option types
- 🔜 Pattern matching
- 🔜 The `?` operator
**Goal:** Ship something you can actually use to solve Go's error handling problem\.
### Phase 2: Ergonomics — 6-8 weeks
- Null safety operators \(`?.`, `??`\)
- Lambda functions \(all styles\)
- Map/filter/reduce
- Tuples
- Type-safe enums
**Goal:** Make writing Dingo feel modern without being weird\.
### Phase 3: Advanced Features — 4-6 weeks
- Immutability
- Ternary operator
- Pattern guards
- Advanced destructuring
**Goal:** Feature parity with Swift/Kotlin for type safety\.
### Phase 4: Language Server — ✅ Complete \(2025-11-20\)
- ✅ gopls proxy working
- ✅ Source maps translating with high accuracy
- ✅ VSCode IDE integration complete
**Goal:** Developer experience that matches or beats regular Go\. ✅ **Achieved**
### Phase 5: Polish & Ship — 4-6 weeks
- Documentation you'll actually read
- Examples that don't suck
- Real-world testing
- Community feedback
**Target:** v1\.0 in about **12-15 months**\.
---
## What Dingo adds \(and what it doesn't\)
### We're adding:
✅ Type safety that prevents bugs at compile time ✅ Modern ergonomics that reduce boilerplate ✅ Features that are proven in Rust/Swift/Kotlin ✅ Zero-cost abstractions \(transpiles to clean Go\)
### We're NOT adding:
❌ Runtime dependencies \(it's just Go underneath\) ❌ Magic behavior \(no operator overloading, fight me\) ❌ Ecosystem fragmentation \(works with all Go packages\) ❌ Unnecessary complexity \(if Go's way is fine, we keep it\)
---
## Standing on the shoulders of giants
Dingo exists because these languages and projects proved it's possible:
**TypeScript** — The blueprint for meta-languages\.
You can add type safety to an existing language without breaking the world\. TypeScript didn't replace JavaScript, it enhanced it\. Millions of developers use it daily\. The entire pattern of "compile to a simpler language" comes from TypeScript's success\.
**Rust** — The gold standard for type safety\.
Result, Option, pattern matching, and the `?` operator are genuinely brilliant\. We're not reinventing this wheel—we're copying Rust's homework because they got an A\+\. Every language that adds these features becomes better\.
**Swift** — Proof that nil safety works\.
Optional types and safe navigation \(`?.`\) made nil pointer bugs almost extinct in Swift codebases\. Apple bet their entire platform on this approach\. It works\. We're bringing it to Go\.
**Kotlin** — Pragmatism over purity\.
Kotlin showed you can add modern features to a language ecosystem \(JVM\) without destroying compatibility\. Null safety, sealed classes, smart casts—all transpile to regular Java bytecode\. Same playbook, different target\.
**Borgo** — The trailblazer that proved Go transpilation works\.
[Borgo](https://github.com/borgo-lang/borgo) \(4\.5k stars\) was the first to prove you can transpile Rust-like syntax to Go successfully\. They showed:
- ✅ Result/Option types work on Go's runtime
- ✅ Pattern matching transpiles cleanly
- ✅ Zero runtime overhead is achievable
- ✅ Real production users want this
Borgo validated the entire concept\. Without Borgo, Dingo wouldn't exist\. We're building on their shoulders, improving the architecture \(gopls integration, source maps, pure Go implementation\), but the core idea? That's Borgo's genius\.
**templ** — The LSP proxy pattern\.
[templ](https://github.com/a-h/templ) showed how to wrap gopls as a language server proxy\. They figured out the hard parts: bidirectional position mapping, protocol translation, maintaining gopls compatibility\. We're using their playbook for Dingo's LSP\.
---
**The common thread:**
Every one of these projects proved that enhancing a language WITHOUT forking it is not only possible—it's the right approach\. TypeScript didn't fork JavaScript\. Kotlin didn't fork Java\. Borgo didn't fork Go\.
Dingo won't either\. We're making Go better by building on top of it\.
---
## Can I help?
Yes\. Here's how:
🌟 **Star the repo** — Shows us people actually want this
💡 **Open issues** — Got ideas? Complaints? Weird edge cases? Tell us\.
📖 **Improve docs** — If something's confusing, it's our fault\. Help us fix it\.
🔨 **Write code** — Check issues tagged "good first issue"
Before you write code, open an issue first\. Let's chat about the approach\. Saves everyone time\.
---
## Project structure \(for the curious\)
dingo/ ├── cmd/dingo/ # CLI tool (dingo build, run, version) ├── pkg/ │ ├── preprocessor/ # Stage 1: Text transformations (Dingo → valid Go) │ │ ├── preprocessor.go # Pipeline coordinator │ │ ├── typeannotation.go # param: Type → param Type │ │ ├── errorprop.go # x? → error handling │ │ ├── enum.go # enum → Go tagged unions │ │ └── keyword.go # Other Dingo keywords │ ├── plugin/ # Stage 2: AST transformations │ │ ├── plugin.go # 3-phase pipeline (Discovery/Transform/Inject) │ │ └── builtin/ │ │ └── result_type.go # Result<T,E> plugin │ ├── generator/ # Code generation (go/printer) │ └── config/ # Configuration ├── tests/golden/ # Golden file tests ├── features/ # Feature proposals (read INDEX.md) ├── docs/ # Documentation └── examples/ # Example code
**Important files:**
- `features/INDEX.md` — Every planned feature with priorities and complexity
- `CLAUDE.md` — Project context \(yes, we use AI for help\)
- `ai-docs/` — Research notes and architecture decisions
---
## Implementation Status
| | | |
| - | - | - |
| ### Core Completed+ **Transpiler Pipeline** + Stage 1: Preprocessor \(regex-based text transforms \+ unique markers\)<br> + Stage 2: go/parser \+ plugin pipeline \(AST transforms\)<br> + Stage 3: Post-AST source maps \(100% accurate position mapping\)<br> + Clean, idiomatic code generation<br> + Full IDE support via precise source maps<br><br>+ **CLI Tooling** + `dingo build` - Transpile files<br> + `dingo run` - Compile and execute<br> + `dingo version` - Version info<br> + Beautiful terminal UI \(lipgloss\)<br><br>+ **Plugin Architecture** + Modular transformation system<br> + Dependency resolution<br> + Extensible design<br><br> | ### Features Implemented **Type System \(Phase 2\.5-2\.7\)**+ Sum Types with `enum`<br>+ Pattern Matching with `match`<br>+ Pattern destructuring \(struct/tuple\)<br>+ IIFE expression wrapping<br>+ Type inference engine<br>+ Configurable nil safety \(off/on/debug\)<br> **Error Handling \(Phase 2\.6\)**+ Error Propagation \(`?` operator\)<br>+ Statement context transformation<br>+ Expression lifting<br>+ Error message wrapping<br>+ Multi-pass AST processing<br> **Functional Programming \(Phase 2\.7\)**+ `map()` - Transform collections<br>+ `filter()` - Predicate selection<br>+ `reduce()` - Aggregation<br>+ `sum()`, `count()`, `all()`, `any()`<br>+ Method chaining support<br>+ Zero-overhead IIFE transpilation<br> | ### Infrastructure & Tooling \(Phase V ✅\) **Documentation \(Complete\)**+ Getting Started Guide<br>+ 5 Feature Guides \(8,000\+ lines\)<br>+ Migration from Go Guide<br>+ Package Management Strategy<br>+ CI/CD Setup Guide<br> **Development Tools**+ Source map validation \(98\.7% accuracy\)<br>+ Workspace builds \(`dingo build ./...`\)<br>+ Golden test diff visualizer<br>+ Performance tracking<br>+ GitHub Actions CI/CD<br> **Package Management**+ Hybrid strategy \(libs→\.go, apps→\.dingo\)<br>+ 3 working example projects<br>+ Dependency resolution<br>+ Incremental build caching<br> **Recently Completed** ✨ **Phase 7: Null Safety Operators**+ ✅ Safe navigation \(`?.`\) - properties and methods<br>+ ✅ Null coalescing \(`??`\) - default values<br>+ ✅ Dual type support \(Option \+ \*T pointers\)<br>+ ✅ Chaining and integration<br> **Planned Features** **Operators & Syntax**+ Ternary operator \(`? :`\)<br>+ Lambda functions \(4 syntax styles\)<br> **Advanced Features**+ Immutability<br>+ Tuples<br>+ Pattern guards<br>+ Type inference enhancements<br> **Future Tooling**+ Language server \(gopls proxy\)<br>+ VS Code extension<br>+ Advanced IDE integration<br> |
### Development Progress
#### ✅ Completed \(v0\.1\.0-alpha\)
| Phase | Status | Features | Results |
| ----- | ------ | -------- | ------- |
| **Phase 0** | ✅ Complete | Research & Architecture | N/A |
| **Phase 1** | ✅ Complete | Transpiler \+ CLI | 100% |
| **Phase 2\.5** | ✅ Complete | Sum Types \+ Pattern Matching | 52/52 tests passing |
| **Phase 2\.6** | ✅ Complete | Result/Option Foundation | 3/8 golden tests |
| **Phase 2\.7** | ✅ Complete | Functional Utilities | 8/8 tests passing |
| **Phase 2\.16** | ✅ Complete | Parser Fix \+ Result Integration | 48/48 preprocessor tests |
| **Phase 3** | ✅ Complete | Fix A4/A5 \+ Option | 261/267 tests \(97\.8%\) |
| **Phase 4** | ✅ Complete | Pattern Matching Enhancements | 57/57 tests passing |
| **Phase V** | ✅ Complete | Infrastructure & Developer Experience | **3/4 external approval** |
| **Phase 6** | ✅ Complete | Lambda Functions \(TypeScript/Rust styles\) | 105/105 tests, 9/9 golden tests |
| **Phase 7** | ✅ Complete | Null Safety Operators \(`?.`, `??`\) | 37/37 tests passing |
**Current Capabilities:** Result\<T,E\>, Option, sum types \(enum\), pattern matching \(Rust/Swift syntax\), error propagation \(?\), functional utilities \(map/filter/reduce\), **lambda functions \(TypeScript/Rust styles\)**, **safe navigation \(?\.\)**, **null coalescing \(??\)**, exhaustiveness checking, workspace builds, source maps \(98\.7% accuracy\)
#### 🚧 Planned for v1\.0 \(Q1 2026\)
| Phase | Priority | Features | Timeline | Status |
| ----- | -------- | -------- | -------- | ------ |
| **Phase 6** | P1 | Lambda Functions \(2 syntax styles\) | 2-3 weeks | ✅ Complete |
| **Phase 7** | P1 | Null Safety Operators \(`?.`, `??`\) | 2 weeks | ✅ Complete |
| **Phase 8** | P2 | Tuples \(single-level, single-line\) | 1-2 weeks | ✅ Complete \(Scope Reduction\)\* |
| **Phase 9** | P2 | Ternary Operator \(`? :`\) | 2-3 days | ✅ Complete \(Implemented in Phase 6\) |
| **Phase 10** | P0 | Language Server \(gopls proxy\) | 8-10 weeks | ✅ Complete \(2025-11-20\) |
| **Phase 11** | P0 | IDE Integration \(VS Code, Neovim\) | 4-6 weeks | ✅ Complete \(VSCode\) |
**v1\.0 Goals:** Production-ready transpiler with full IDE support, comprehensive documentation, and battle-tested core features
#### 🔮 Post-v1\.0 Roadmap
| Phase | Priority | Features | Timeline | Community Demand |
| ----- | -------- | -------- | -------- | ---------------- |
| **v1\.1** | P2 | Immutability \(const tracking\) | 4\+ weeks | ⭐⭐⭐ |
| **v1\.1** | P3 | Default Parameters | 2 weeks | ⭐⭐ |
| **v1\.2** | P4 | Function Overloading | 3 weeks | ⭐⭐ \(specialized use\) |
| **v1\.2** | P4 | Operator Overloading | 2 weeks | ⭐⭐ \(math/DSL users\) |
| **Future** | P5 | Async/await sugar | TBD | ⭐⭐⭐ |
| **Future** | P5 | Macros/metaprogramming | TBD | ⭐⭐ |
**Legend:** 🔴 Not Started \| 🟡 In Progress \| 🟢 In Development \| ✅ Complete
**Phase V Deliverables \(202