- name
- dingo-developer
- description
- Generates and reviews Dingo meta-language code with Go boundary handling. Use when the user mentions Dingo, .dingo files, or Dingo-to-Go transpilation.
# Dingo Developer
## Overview
Dingo is a meta-language for Go that transpiles `.dingo` files to idiomatic `.go` files. It
provides modern language features — Result/Option types, sum types (enums), pattern matching,
error propagation, safe navigation, null coalescing, lambdas, generics, and tuples — while
maintaining 100% Go ecosystem compatibility.
**Repository:** https://github.com/MadAppGang/dingo
**Key philosophy:** Dingo is syntax sugar over Go, not a new type system. The transpiled Go
code is clean and idiomatic. All Go syntax remains valid in `.dingo` files.
**When to use Dingo:**
- You want concise error handling with the `?` operator
- You need sum types (enums) and pattern matching
- You prefer functional patterns with lambdas
- You want `Option[T]` and `Result[T, E]` types
- You need safe navigation (`?.`) and null coalescing (`??`)
**CLI commands:**
```bash
dingo build ./cmd/app # Transpile + compile; explicit packages only, not ./...
dingo run main.dingo # Transpile + run directly
dingo go ./... # Generate .go files only, into build/ (no go.mod there)
```
Build directory, checking a project, CI, editor setup and transpile errors:
`references/build-and-tooling.md` (relative to this file).
---
## Error Handling
### The `?` Operator — Three Variants
**Decision tree — pick the first that applies:**
1. Just propagate the error unchanged → plain `?`
2. Add lightweight string context → `? "message"`
3. Build a custom error type or capture variables → `? |e| expr`
4. Need side effects or multi-statement logic → `guard` (see below)
5. Absence is expected, not an error → use `Option[T]` + `guard`
---
#### Variant 1: Plain Propagation
```go
// BEFORE (Go)
user, err := getUser(id)
if err != nil {
return nil, err
}
```
```dingo
// AFTER (Dingo)
user := getUser(id)?
```
---
#### Variant 2: String Context
Generates `fmt.Errorf("message: %w", err)`.
```go
// BEFORE (Go)
config, err := loadConfig(path)
if err != nil {
return nil, fmt.Errorf("load config: %w", err)
}
```
```dingo
// AFTER (Dingo)
config := loadConfig(path) ? "load config"
```
---
#### Variant 3: Lambda Transform
Captures local variables and builds custom error types. Both Rust-style (`|e|`) and
TypeScript-style (`e =>`) are valid.
```go
// BEFORE (Go)
resp, err := httpClient.Get(url)
if err != nil {
return nil, &NetworkError{URL: url, Err: err}
}
```
```dingo
// AFTER — Rust-style
resp := httpClient.Get(url) ? |e| &NetworkError{URL: url, Err: e}
// AFTER — TypeScript-style (identical semantics)
resp := httpClient.Get(url) ? e => &NetworkError{URL: url, Err: e}
```
---
### Function Signature Compatibility
| Return type | `?` works? | Notes |
|---|---|---|
| `(T, error)` | YES | Most common |
| `(T1, T2, error)` | YES | Extracts last value |
| `Result[T, E]` | YES | Unwraps to T |
| `error` only (void success) | YES | Propagates error directly |
| `(T, bool)` | NO | Use `Option[T]` + guard |
| `T` only (no error) | NO | Nothing to propagate |
**`?` in void functions is a compile error.** If you need to propagate from a
void-returning call, acknowledge explicitly:
```dingo
// WRONG — compile error when function returns void
validateInput(data)?
// CORRECT
_ = validateInput(data)?
```
---
### `guard` Statement
`guard` provides early-return logic for `Result[T, E]` and `Option[T]` values. It is the
idiomatic way to flatten a "pyramid of doom" into a sequential happy path.
**CRITICAL CONSTRAINT:** `guard` ONLY works with `Result[T, E]` or `Option[T]` return types.
It does NOT work on plain booleans, integers, or other types.
```dingo
// WRONG — guard on boolean generates invalid Go (compile error)
guard user.Active else {
return "inactive"
}
// CORRECT — guard on Option[T]
guard profile := getProfile(userID) else {
return "no profile"
}
// CORRECT — guard on Result[T, E]
guard user := findUser(id) else |err| {
return fmt.Errorf("find user: %w", err)
}
```
#### Guard Syntax Reference
```dingo
// Result: bind error value with |err|
guard user := findUser(id) else |err| {
return Err(fmt.Errorf("find user: %w", err))
}
// Option: no |err| binding (None has no error value)
guard theme := getTheme(id) else {
return "default"
}
// Reassignment: use = not := when variable already declared
guard user = refreshUser(ctx, user.ID) else |err| {
return Err(err)
}
// Tuple destructuring
guard (lat, lng) := getCoords(address) else |err| {
return Err(fmt.Errorf("coords: %w", err))
}
```
#### Pyramid of Doom → Flat Guard Chain
```go
// BEFORE (Go) — nested error ladder
user, err := findUser(id)
if err != nil {
return "", fmt.Errorf("find user: %w", err)
}
profile, err := loadProfile(user.ID)
if err != nil {
return "", fmt.Errorf("load profile: %w", err)
}
settings, err := loadSettings(profile.ID)
if err != nil {
return "", fmt.Errorf("load settings: %w", err)
}
```
```dingo
// AFTER (Dingo) — flat guard chain
guard user := findUser(id) else |e| { return "", fmt.Errorf("find user: %w", e) }
guard profile := loadProfile(user.ID) else |e| { return "", fmt.Errorf("load profile: %w", e) }
guard settings := loadSettings(profile.ID) else |e| { return "", fmt.Errorf("load settings: %w", e) }
```
---
### `?` in Goroutines
The `?` operator cannot propagate errors from inside goroutines (there is no enclosing
function to return to). Use channels or `sync.WaitGroup` + error collection instead.
```dingo
// WRONG — ? inside goroutine has no propagation target
go func() {
result := fetch()? // compile error or logic error
}()
// CORRECT — explicit error handling inside goroutines
errCh := make(chan error, 1)
go func() {
result, err := fetch()
if err != nil {
errCh <- err
return
}
process(result)
errCh <- nil
}()
if err := <-errCh; err != nil {
return nil, err
}
```
---
## Result & Option Types
### Result[T, E]
Use for operations that can fail when callers need to act on the error type.
**Constructors:** `Ok[T, E](value)` and `Err[T, E](err)`
**Methods (complete list):**
| Method | Description |
|---|---|
| `.IsOk()` | Returns true if Ok variant |
| `.IsErr()` | Returns true if Err variant |
| `.MustOk()` | Returns value; panics if Err |
| `.MustErr()` | Returns error; panics if Ok |
| `.OkOr(default)` | Returns value or default if Err |
**NOT available:** `.Unwrap()`, `.Map()`, `.FlatMap()`, `.UnwrapErr()` — these do not exist
in the dgo package. Do not use them.
```dingo
func divide(a, b int) Result[int, string] {
if b == 0 {
return Err[int, string]("division by zero")
}
return Ok[int, string](a / b)
}
result := divide(10, 2)
if result.IsOk() {
fmt.Println(result.MustOk()) // 5
}
// Safe extraction with default
value := divide(10, 0).OkOr(0) // 0
```
#### Result in Service Layer
```dingo
func (s *UserService) FindByID(ctx context.Context, id string) Result[User, error] {
guard user := s.repo.FindByID(ctx, id) else |err| {
if errors.Is(err, sql.ErrNoRows) {
return Err[User, error](NotFoundError("user"))
}
return Err[User, error](InternalError(err))
}
return Ok[User, error](user)
}
```
---
### Option[T]
Use when absence is expected and normal — not an error condition.
**Constructors:** `Some(value)` and `None[T]()`
**Methods (complete list):**
| Method | Description |
|---|---|
| `.IsSome()` | Returns true if Some variant |
| `.IsNone()` | Returns true if None variant |
| `.MustSome()` | Returns value; panics if None |
| `.SomeOr(default)` | Returns value or default |
| `.SomeOrElse(fn)` | Returns value or calls fn() for default |
**NOT available:** `.Unwrap()`, `.Map()`, `.FlatMap()` — these do not exist in the dgo
package. Do not use them.
```dingo
func findUser(id string) Option[User] {
user, err := db.FindByID(id)
if err != nil {
return None[User]()
}
return Some(user)
}
user := findUser("123")
if user.IsSome() {
fmt.Println(user.MustSome().Name)
}
// Safe extraction with default
name := findUser("123").SomeOr(User{Name: "Anonymous"}).Name
// Lazy default (only computed when None)
name := findUser("123").SomeOrElse(func() User { return buildDefaultUser() }).Name
```
---
### Result vs Option — When to Use Which
| Situation | Use |
|---|---|
| Absence is expected and normal ("not found") | `Option[T]` |
| Absence needs explanation to the caller | `Result[T, E]` |
| Infrastructure/stdlib calls | `(T, error)` + `?` |
| Domain API with typed errors | `Result[T, DomainError]` |
| "Find" queries on collections | `Option[T]` |
| "Create/Update" domain operations | `Result[T, error]` |
**Rule:** If absence is the expected outcome (e.g., cache miss, optional config), use
`Option[T]`. If absence would surprise the caller and they need to know why, use `Result[T, E]`.
Do NOT mix: a function should return either `Result[T, E]` or `(T, error)` — not both.
`func f() (Result[User, string], error)` is always wrong.
---
## Enums & Sum Types
### Enum Declaration
```dingo
// Simple variants (no data)
enum Status {
Pending
Active
Done
}
// Variants with data
enum Event {
UserCreated { userID: int, email: string }
UserDeleted { userID: int }
EmailChanged { userID: int, oldEmail: string, newEmail: string }
}
// Mixed
enum APIResponse {
Success { transactionID: string, amount: float64 }
NotFound
Error { code: int, reason: string }
}
```
### Constructor Naming
Constructors are auto-generated as `New{EnumName}{VariantName}(fieldValues...)`.
```dingo
// Simple variants (no constructor call needed — use as value)
status := Status.Pending
// Variants with data — use generated constructor
event := NewEventUserCreated(42, "user@example.com")
deleted := NewEventUserDeleted(42)
resp := NewAPIResponseSuccess("TXN-123", 99.99)
notFound := NewAPIResponseNotFound()
```
### What Enums Generate
Each enum declaration generates:
- A Go interface with a marker method
- One struct per variant containing the variant's fields
- Constructor functions `New{Enum}{Variant}(...)`
```go
Auf GitHub ansehen