Agents convert-typescript-golang
Convert TypeScript code to idiomatic Go. Use when migrating TypeScript projects to Go, translating TypeScript patterns to idiomatic Go, or refactoring TypeScript codebases into Go. Extends meta-convert-dev with TypeScript-to-Go specific patterns.
git clone https://github.com/aRustyDev/agents
T=$(mktemp -d) && git clone --depth=1 https://github.com/aRustyDev/agents "$T" && mkdir -p ~/.claude/skills && cp -r "$T/content/skills/convert-typescript-golang" ~/.claude/skills/arustydev-agents-convert-typescript-golang && rm -rf "$T"
content/skills/convert-typescript-golang/SKILL.mdConvert TypeScript to Go
Convert TypeScript code to idiomatic Go. This skill extends
meta-convert-dev with TypeScript-to-Go specific type mappings, idiom translations, and tooling.
This Skill Extends
- Foundational conversion patterns (APTV workflow, testing strategies)meta-convert-dev
For general concepts like the Analyze → Plan → Transform → Validate workflow, testing strategies, and common pitfalls, see the meta-skill first.
This Skill Adds
- Type mappings: TypeScript types → Go types
- Idiom translations: TypeScript patterns → idiomatic Go
- Error handling: Exceptions → error return values
- Async patterns: Promise/async → goroutines/channels
- Interface patterns: Structural typing → Go interfaces
This Skill Does NOT Cover
- General conversion methodology - see
meta-convert-dev - TypeScript language fundamentals - see
lang-typescript-dev - Go language fundamentals - see
lang-golang-dev - Reverse conversion (Go → TypeScript) - see
convert-golang-typescript
Quick Reference
| TypeScript | Go | Notes |
|---|---|---|
| | Direct mapping |
| , , | Specify precision |
| | Direct mapping |
| or zero value | Go has nil for pointers/interfaces/slices/maps/channels |
| | Slice (dynamic array) |
| | Slice |
| | Named fields preferred over tuples |
| | Map type |
| | Map type |
| or | Set via map with empty struct |
| or custom type | Use type switch or discriminated union pattern |
| or function with error return | Goroutines for async execution |
| | Behavior contracts |
| + methods | Methods with receiver syntax |
| block with | Or string constants |
| or (Go 1.18+) | Avoid when possible |
| No return value | Function signature omits return |
| No direct equivalent | Use panic or infinite loop |
When Converting Code
- Analyze source thoroughly before writing target
- Map types first - create type equivalence table
- Preserve semantics over syntax similarity
- Adopt target idioms - don't write "TypeScript code in Go syntax"
- Handle edge cases - null/nil/undefined, error paths, resource cleanup
- Test equivalence - same inputs → same outputs
Type System Mapping
Primitive Types
| TypeScript | Go | Notes |
|---|---|---|
| | UTF-8 encoded in Go |
| | Default for integers without decimal |
| , , , | Sized integers |
| , , , , | Unsigned integers |
| , | Floating point |
| | From package |
| | Direct mapping |
| | For pointers, interfaces, slices, maps, channels, functions |
| Zero value | Each type has a zero value (0, "", false, nil) |
| No direct equivalent | Use string or int constants |
| or | alias added in Go 1.18 |
| with type assertion | Requires type checking |
| (no return) | Function returns nothing |
| No direct equivalent | Functions that never return use panic |
Collection Types
| TypeScript | Go | Notes |
|---|---|---|
| | Slice (resizable, passed by reference) |
| | Same as T[] |
| | Go doesn't enforce readonly at compile time |
| | Fixed-size array |
| | Named struct preferred |
| | Named struct preferred |
| | Hash map |
| | Hash map |
| | Empty struct uses zero memory |
| | Alternative using bool (1 byte per entry) |
| No direct equivalent | Use map with manual cleanup |
| No direct equivalent | Use map with manual cleanup |
Composite Types
| TypeScript | Go | Notes |
|---|---|---|
(data) | | Data structures |
| | Behavior contracts |
| + methods | Struct with receiver methods |
| | Type alias (Go 1.9+) |
| Custom type with methods | Discriminated union pattern |
| | Pointer can be nil |
| or zero value check | Pointer or explicit check |
| Struct with pointer fields | Each field can be nil |
| Struct with value fields | All fields have values |
| New struct type | Select fields manually |
| New struct type | Exclude fields manually |
| block with | Or typed constants |
| | Package organization |
Generic Type Mappings
| TypeScript | Go | Notes |
|---|---|---|
| | Generic type parameter (Go 1.18+) |
| | Type constraint using interface |
| No direct equivalent | Use reflection or code generation |
| | Built-in generic slice |
| or function return | Channels for async communication |
| No language support | Convention and documentation |
| | Built-in generic map |
Idiom Translation
Pattern: Null/Undefined Handling
TypeScript:
const name = user?.name ?? "Anonymous"; const age = user?.age || 18;
Go:
var name string if user != nil && user.Name != "" { name = user.Name } else { name = "Anonymous" } age := 18 if user != nil && user.Age > 0 { age = user.Age }
Why this translation:
- Go doesn't have optional chaining or null coalescing operators
- Explicit nil checks are idiomatic and clear
- Zero values (0, "", false) should be considered in logic
- Pointers are used when nil is a meaningful state
Pattern: Array/Slice Operations
TypeScript:
const activeValues = items .filter(x => x.active) .map(x => x.value) .reduce((sum, val) => sum + val, 0);
Go:
var activeValues int for _, item := range items { if item.Active { activeValues += item.Value } }
Why this translation:
- Go prefers explicit for loops over chaining methods
- More readable and maintainable for Go developers
- Better performance (single pass, no intermediate allocations)
- Can use generics (Go 1.18+) for reusable map/filter if needed
Pattern: Object Destructuring
TypeScript:
const { name, age, ...rest } = user; const [first, second, ...others] = items;
Go:
name := user.Name age := user.Age // rest requires manual field copying or reflection first := items[0] second := items[1] others := items[2:]
Why this translation:
- Go doesn't support destructuring
- Direct field access is clear and explicit
- Slice operations provide array destructuring
- Manual copying ensures clarity about what's being used
Pattern: Classes and Methods
TypeScript:
class Calculator { private total: number = 0; add(value: number): void { this.total += value; } getTotal(): number { return this.total; } }
Go:
type Calculator struct { total int // unexported (lowercase) is private } func NewCalculator() *Calculator { return &Calculator{total: 0} } func (c *Calculator) Add(value int) { c.total += value } func (c *Calculator) GetTotal() int { return c.total }
Why this translation:
- Go uses struct types instead of classes
- Methods have receiver syntax (c *Calculator)
- Constructor pattern uses New* functions
- Exported/unexported controlled by capitalization
- Pointer receivers allow mutation
Pattern: Interfaces and Duck Typing
TypeScript:
interface Drawable { draw(): void; } function render(item: Drawable): void { item.draw(); } // Any object with draw() method satisfies interface const circle = { draw: () => console.log("circle") }; render(circle);
Go:
type Drawable interface { Draw() } func Render(item Drawable) { item.Draw() } // Explicit type must implement interface type Circle struct{} func (c Circle) Draw() { fmt.Println("circle") } // Usage circle := Circle{} Render(circle)
Why this translation:
- Both languages support structural typing for interfaces
- Go requires explicit types, not object literals
- Interface satisfaction is implicit (no implements keyword)
- Method names must match exactly (case-sensitive)
Pattern: Default Parameters
TypeScript:
function greet(name: string = "Guest", greeting: string = "Hello"): string { return `${greeting}, ${name}!`; }
Go:
func Greet(name, greeting string) string { if name == "" { name = "Guest" } if greeting == "" { greeting = "Hello" } return fmt.Sprintf("%s, %s!", greeting, name) } // Or use options pattern for complex cases type GreetOptions struct { Name string Greeting string } func GreetWithOptions(opts GreetOptions) string { if opts.Name == "" { opts.Name = "Guest" } if opts.Greeting == "" { opts.Greeting = "Hello" } return fmt.Sprintf("%s, %s!", opts.Greeting, opts.Name) }
Why this translation:
- Go doesn't support default parameters
- Check zero values and provide defaults explicitly
- Options pattern for complex parameter sets
- Functional options pattern for even more flexibility
Pattern: Spread Operator
TypeScript:
const arr1 = [1, 2, 3]; const arr2 = [...arr1, 4, 5]; const obj1 = { a: 1, b: 2 }; const obj2 = { ...obj1, c: 3 };
Go:
arr1 := []int{1, 2, 3} arr2 := append(append([]int{}, arr1...), 4, 5) // Or clearer: arr2 := make([]int, len(arr1), len(arr1)+2) copy(arr2, arr1) arr2 = append(arr2, 4, 5) // No built-in object spread; must copy manually obj2 := struct{ A, B, C int }{ A: obj1.A, B: obj1.B, C: 3, }
Why this translation:
- Go uses append with ... for variadic slice expansion
- Pre-allocating capacity avoids reallocation
- No object spread; manual field copying required
- Reflection can help for generic copying but adds complexity
Pattern: Optional Properties
TypeScript:
interface User { name: string; email?: string; age?: number; }
Go:
type User struct { Name string Email *string // pointer indicates optional Age *int // nil means not provided } // Helper to create pointer func StringPtr(s string) *string { return &s } func IntPtr(i int) *int { return &i } // Usage user := User{ Name: "Alice", Email: StringPtr("alice@example.com"), }
Why this translation:
- Pointers distinguish between "not provided" (nil) and "zero value"
- Helper functions make pointer creation cleaner
- Alternative: use zero values and a separate "set" map
- Consider whether nil vs zero value distinction is needed
Pattern: Union Types
TypeScript:
type Result = { success: true; data: string } | { success: false; error: string }; function process(): Result { if (Math.random() > 0.5) { return { success: true, data: "OK" }; } return { success: false, error: "Failed" }; }
Go:
type Result struct { Success bool Data string // only valid if Success == true Error string // only valid if Success == false } func Process() Result { if rand.Float64() > 0.5 { return Result{Success: true, Data: "OK"} } return Result{Success: false, Error: "Failed"} } // Or use interface with type assertion type ResultSuccess struct{ Data string } type ResultError struct{ Error string } func Process() interface{} { if rand.Float64() > 0.5 { return ResultSuccess{Data: "OK"} } return ResultError{Error: "Failed"} }
Why this translation:
- Go doesn't have union types
- Use struct with discriminator field (Success bool)
- Interface{} with type assertion for true sum types
- Consider if error return pattern is more idiomatic
Pattern: String Interpolation
TypeScript:
const name = "Alice"; const age = 30; const message = `Hello, ${name}! You are ${age} years old.`;
Go:
name := "Alice" age := 30 message := fmt.Sprintf("Hello, %s! You are %d years old.", name, age)
Why this translation:
- Go uses fmt.Sprintf for string formatting
- Printf-style format verbs (%s, %d, %v, etc.)
- Type-safe at runtime, not compile-time
- Alternative: strings.Builder for complex concatenation
Error Handling
TypeScript Exception Model → Go Error Return Model
TypeScript:
function parseConfig(path: string): Config { if (!fs.existsSync(path)) { throw new Error(`Config file not found: ${path}`); } const content = fs.readFileSync(path, 'utf-8'); try { return JSON.parse(content); } catch (e) { throw new Error(`Failed to parse config: ${e.message}`); } } // Usage try { const config = parseConfig("config.json"); console.log(config); } catch (err) { console.error("Error:", err.message); }
Go:
func ParseConfig(path string) (*Config, error) { if _, err := os.Stat(path); os.IsNotExist(err) { return nil, fmt.Errorf("config file not found: %s", path) } content, err := os.ReadFile(path) if err != nil { return nil, fmt.Errorf("failed to read config: %w", err) } var config Config if err := json.Unmarshal(content, &config); err != nil { return nil, fmt.Errorf("failed to parse config: %w", err) } return &config, nil } // Usage config, err := ParseConfig("config.json") if err != nil { log.Printf("Error: %v", err) return } fmt.Println(config)
Why this translation:
- Go returns errors as values, not exceptions
- Multiple return values: (result, error)
- fmt.Errorf with %w wraps errors (Go 1.13+)
- errors.Is and errors.As for error checking
- Caller checks err != nil after each call
Custom Error Types
TypeScript:
class ValidationError extends Error { constructor(public field: string, message: string) { super(message); this.name = "ValidationError"; } } class NotFoundError extends Error { constructor(public resource: string) { super(`${resource} not found`); this.name = "NotFoundError"; } } function validateUser(user: User): void { if (!user.email) { throw new ValidationError("email", "Email is required"); } }
Go:
// Custom error types implement error interface type ValidationError struct { Field string Message string } func (e *ValidationError) Error() string { return fmt.Sprintf("validation error on field %s: %s", e.Field, e.Message) } type NotFoundError struct { Resource string } func (e *NotFoundError) Error() string { return fmt.Sprintf("%s not found", e.Resource) } func ValidateUser(user *User) error { if user.Email == "" { return &ValidationError{Field: "email", Message: "Email is required"} } return nil } // Usage with type assertion err := ValidateUser(user) if err != nil { var validationErr *ValidationError if errors.As(err, &validationErr) { log.Printf("Validation failed on field: %s", validationErr.Field) } }
Why this translation:
- Go uses error interface (Error() string method)
- Custom error types are structs with Error() method
- errors.As for type-safe error checking
- errors.Is for sentinel error comparison
- Wrap errors with fmt.Errorf("%w", err) to preserve chain
Panic vs Error Returns
TypeScript:
// Exceptions for everything function divide(a: number, b: number): number { if (b === 0) { throw new Error("division by zero"); } return a / b; }
Go:
// Error returns for expected errors func Divide(a, b float64) (float64, error) { if b == 0 { return 0, errors.New("division by zero") } return a / b, nil } // Panic only for programmer errors (bugs) func DivideMustNotBeZero(a, b float64) float64 { if b == 0 { panic("division by zero - caller error") } return a / b }
Why this translation:
- Go distinguishes expected errors (return) from bugs (panic)
- Use error returns for conditions caller should handle
- Use panic for programmer errors / assertions
- recover() can catch panics (similar to catch) but rarely used
- Idiomatic Go: errors are values, not exceptions
Concurrency Patterns
Promise → Goroutines with Channels
TypeScript:
async function fetchUser(id: string): Promise<User> { const response = await fetch(`/users/${id}`); return response.json(); } // Usage const user = await fetchUser("123"); console.log(user);
Go:
func FetchUser(id string) (*User, error) { resp, err := http.Get(fmt.Sprintf("/users/%s", id)) if err != nil { return nil, err } defer resp.Body.Close() var user User if err := json.NewDecoder(resp.Body).Decode(&user); err != nil { return nil, err } return &user, nil } // Usage (synchronous) user, err := FetchUser("123") if err != nil { log.Fatal(err) } fmt.Println(user) // Or asynchronous with goroutine func FetchUserAsync(id string) <-chan *User { ch := make(chan *User, 1) go func() { user, err := FetchUser(id) if err != nil { log.Printf("Error: %v", err) close(ch) return } ch <- user }() return ch } // Usage userChan := FetchUserAsync("123") user := <-userChan // Wait for result
Why this translation:
- Go uses goroutines (lightweight threads) for concurrency
- Channels communicate between goroutines
- Synchronous by default; explicit goroutines for async
- defer ensures cleanup (like finally)
- Error handling remains explicit
Promise.all → WaitGroup or Channels
TypeScript:
const [users, posts, comments] = await Promise.all([ fetchUsers(), fetchPosts(), fetchComments() ]);
Go:
// Using WaitGroup var wg sync.WaitGroup var users []User var posts []Post var comments []Comment var mu sync.Mutex // Protect shared state if needed var errs []error wg.Add(3) go func() { defer wg.Done() u, err := FetchUsers() if err != nil { mu.Lock() errs = append(errs, err) mu.Unlock() return } mu.Lock() users = u mu.Unlock() }() go func() { defer wg.Done() p, err := FetchPosts() if err != nil { mu.Lock() errs = append(errs, err) mu.Unlock() return } mu.Lock() posts = p mu.Unlock() }() go func() { defer wg.Done() c, err := FetchComments() if err != nil { mu.Lock() errs = append(errs, err) mu.Unlock() return } mu.Lock() comments = c mu.Unlock() }() wg.Wait() if len(errs) > 0 { // Handle errors } // Or using channels (cleaner) type Result struct { Users []User Posts []Post Comments []Comment } func FetchAll() (*Result, error) { usersCh := make(chan []User, 1) postsCh := make(chan []Post, 1) commentsCh := make(chan []Comment, 1) errCh := make(chan error, 3) go func() { users, err := FetchUsers() if err != nil { errCh <- err return } usersCh <- users }() go func() { posts, err := FetchPosts() if err != nil { errCh <- err return } postsCh <- posts }() go func() { comments, err := FetchComments() if err != nil { errCh <- err return } commentsCh <- comments }() result := &Result{} for i := 0; i < 3; i++ { select { case users := <-usersCh: result.Users = users case posts := <-postsCh: result.Posts = posts case comments := <-commentsCh: result.Comments = comments case err := <-errCh: return nil, err } } return result, nil }
Why this translation:
- sync.WaitGroup waits for multiple goroutines
- Channels communicate results between goroutines
- select statement waits on multiple channels
- Explicit error handling for each operation
- Consider errgroup package for cleaner error handling
Async/Await → Goroutines
TypeScript:
async function processItems(items: string[]): Promise<string[]> { const results: string[] = []; for (const item of items) { const result = await processItem(item); results.push(result); } return results; }
Go:
// Sequential (like await) func ProcessItems(items []string) ([]string, error) { results := make([]string, 0, len(items)) for _, item := range items { result, err := ProcessItem(item) if err != nil { return nil, err } results = append(results, result) } return results, nil } // Concurrent (parallel processing) func ProcessItemsConcurrent(items []string) ([]string, error) { results := make([]string, len(items)) errCh := make(chan error, len(items)) var wg sync.WaitGroup for i, item := range items { wg.Add(1) go func(index int, item string) { defer wg.Done() result, err := ProcessItem(item) if err != nil { errCh <- err return } results[index] = result }(i, item) } wg.Wait() close(errCh) if len(errCh) > 0 { return nil, <-errCh } return results, nil }
Why this translation:
- Go doesn't have async/await keywords
- Sequential code is default (like synchronous)
- Use goroutines explicitly for concurrency
- Channels or WaitGroup coordinate goroutines
- More control over concurrency patterns
Event Emitters → Channels
TypeScript:
import { EventEmitter } from 'events'; const emitter = new EventEmitter(); emitter.on('data', (value: number) => { console.log('Received:', value); }); emitter.emit('data', 42);
Go:
// Channel-based pub/sub type EventBus struct { subscribers []chan int mu sync.RWMutex } func NewEventBus() *EventBus { return &EventBus{ subscribers: make([]chan int, 0), } } func (eb *EventBus) Subscribe() <-chan int { eb.mu.Lock() defer eb.mu.Unlock() ch := make(chan int, 10) // buffered eb.subscribers = append(eb.subscribers, ch) return ch } func (eb *EventBus) Publish(value int) { eb.mu.RLock() defer eb.mu.RUnlock() for _, ch := range eb.subscribers { ch <- value } } // Usage bus := NewEventBus() dataCh := bus.Subscribe() go func() { for value := range dataCh { fmt.Println("Received:", value) } }() bus.Publish(42)
Why this translation:
- Go uses channels for message passing
- Explicit subscriber management
- Type-safe channels (type per channel)
- Consider existing packages (e.g., EventBus libraries)
- Goroutines handle async listeners
Common Pitfalls
1. Ignoring Error Returns
Problem:
// ❌ Ignoring error return user, _ := FetchUser("123") fmt.Println(user.Name) // Potential nil pointer dereference!
Solution:
// ✓ Always check errors user, err := FetchUser("123") if err != nil { return fmt.Errorf("failed to fetch user: %w", err) } fmt.Println(user.Name)
Why:
- Go conventions require explicit error handling
- Ignoring errors leads to runtime panics
- Use linters (errcheck, golangci-lint) to catch
2. Using Pointers When Not Needed
Problem:
// ❌ Unnecessary pointer func Double(n *int) *int { result := *n * 2 return &result }
Solution:
// ✓ Pass by value for small types func Double(n int) int { return n * 2 }
Why:
- Small types (int, bool, small structs) are efficient by value
- Pointers add indirection and nil checks
- Use pointers for: large structs, mutation, or optional values
3. Modifying Loop Variables in Goroutines
Problem:
// ❌ Loop variable capture bug for _, item := range items { go func() { process(item) // All goroutines see last item! }() }
Solution:
// ✓ Pass variable as parameter or shadow it for _, item := range items { item := item // shadow go func() { process(item) }() } // Or pass as parameter for _, item := range items { go func(i string) { process(i) }(item) }
Why:
- Loop variable is reused across iterations
- Goroutines capture variable reference, not value
- Fixed in Go 1.22+ with per-iteration variables
4. Not Closing Channels
Problem:
// ❌ Channel never closed ch := make(chan int) go func() { for i := 0; i < 10; i++ { ch <- i } // Never closes! }() for val := range ch { fmt.Println(val) // Hangs after 10 values }
Solution:
// ✓ Close channel when done ch := make(chan int) go func() { defer close(ch) for i := 0; i < 10; i++ { ch <- i } }() for val := range ch { fmt.Println(val) }
Why:
- range on channel blocks until closed
- close() signals no more values coming
- Only sender should close (not receiver)
5. Misunderstanding Zero Values
Problem:
// TypeScript: undefined check if (user.age !== undefined) { // age was explicitly set }
// ❌ Go: can't distinguish zero value from explicit zero if user.Age != 0 { // Could be unset OR explicitly set to 0! }
Solution:
// ✓ Use pointers for optional values type User struct { Name string Age *int // nil means not set, 0 means explicitly zero } if user.Age != nil { fmt.Println(*user.Age) }
Why:
- Go initializes all variables to zero values
- Can't distinguish "not set" from "set to zero"
- Use pointers when distinction matters
6. Forgetting defer for Cleanup
Problem:
// ❌ Manual cleanup easy to forget file, err := os.Open("file.txt") if err != nil { return err } // ... lots of code ... if someError { return someError // Forgot to close file! } file.Close()
Solution:
// ✓ defer ensures cleanup file, err := os.Open("file.txt") if err != nil { return err } defer file.Close() // Always runs before function returns // ... code can return anywhere ...
Why:
- defer guarantees cleanup on all return paths
- Executes in LIFO order
- Common for: files, mutexes, database connections
7. Copying Mutexes
Problem:
// ❌ Copying struct with mutex type Counter struct { mu sync.Mutex count int } func (c Counter) Inc() { // Value receiver copies mutex! c.mu.Lock() defer c.mu.Unlock() c.count++ }
Solution:
// ✓ Pointer receiver for structs with mutexes func (c *Counter) Inc() { c.mu.Lock() defer c.mu.Unlock() c.count++ }
Why:
- Copying a locked mutex is undefined behavior
- sync types (Mutex, WaitGroup, etc.) must not be copied
- Use pointer receivers for types with sync primitives
8. Interface nil Confusion
Problem:
// ❌ Interface containing nil pointer isn't nil! var p *User = nil var i interface{} = p if i != nil { fmt.Println("Not nil!") // This prints! }
Solution:
// ✓ Check for nil before assigning to interface var p *User = nil if p != nil { i = p } else { i = nil // Or don't assign } // Or use typed nil check var i interface{} = (*User)(nil) if i == nil || i.(*User) == nil { // Actually nil }
Why:
- Interface stores (type, value) pair
- (type=*User, value=nil) != (type=nil, value=nil)
- Common source of bugs in error returns
Tooling
Conversion Tools
| Tool | Purpose | Notes |
|---|---|---|
| Manual | TypeScript → Go | No mature automated converter exists |
| AST analysis | Parse TypeScript | Use TypeScript compiler API |
| Code generation | Generate Go | Template-based or custom tooling |
Go Development Tools
| Tool | Purpose | Notes |
|---|---|---|
| Code formatting | Standardizes formatting |
| Import management | Adds/removes imports automatically |
| Linting | Comprehensive linter aggregator |
| Static analysis | Detects common mistakes |
| Advanced static analysis | High-quality checks |
| Error checking | Ensures errors are checked |
| Testing | Built-in test framework |
| Race detection | Detects race conditions |
| Profiling | CPU and memory profiling |
| Debugging | Go debugger |
Testing Tools
| Tool | Purpose | Notes |
|---|---|---|
package | Unit tests | Built-in framework |
| Assertions | Popular testing toolkit |
| Mocking | Interface mocking |
| HTTP testing | Test HTTP handlers |
| Deep comparison | Compare complex structures |
Dependency Management
| TypeScript (npm) | Go Equivalent | Notes |
|---|---|---|
| , | HTTP clients |
| Built-ins | Go has many built-in utilities |
| , , | Web frameworks |
| | Built-in testing |
| , | Config management |
| , , | Logging |
| , , | CLI parsing |
| | Date/time built-in |
| | UUID generation |
| , | Validation |
Examples
Example 1: Simple - Type and Function Translation
Before (TypeScript):
interface User { id: string; name: string; age: number; email?: string; } function findUserByID(users: User[], id: string): User | undefined { return users.find(u => u.id === id); } // Usage const users: User[] = [ { id: "1", name: "Alice", age: 30, email: "alice@example.com" }, { id: "2", name: "Bob", age: 25 }, ]; const user = findUserByID(users, "1"); if (user) { console.log(`Found: ${user.name}`); }
After (Go):
package main import "fmt" type User struct { ID string Name string Age int Email *string // pointer for optional field } func FindUserByID(users []User, id string) *User { for i := range users { if users[i].ID == id { return &users[i] } } return nil } func main() { email := "alice@example.com" users := []User{ {ID: "1", Name: "Alice", Age: 30, Email: &email}, {ID: "2", Name: "Bob", Age: 25, Email: nil}, } user := FindUserByID(users, "1") if user != nil { fmt.Printf("Found: %s\n", user.Name) } }
Example 2: Medium - Error Handling and JSON
Before (TypeScript):
import * as fs from 'fs'; interface Config { host: string; port: number; debug: boolean; } class ConfigError extends Error { constructor(message: string) { super(message); this.name = "ConfigError"; } } function loadConfig(path: string): Config { if (!fs.existsSync(path)) { throw new ConfigError(`Config file not found: ${path}`); } const content = fs.readFileSync(path, 'utf-8'); try { const config = JSON.parse(content); if (!config.host || typeof config.port !== 'number') { throw new ConfigError("Invalid config format"); } return config as Config; } catch (e) { if (e instanceof ConfigError) { throw e; } throw new ConfigError(`Failed to parse config: ${(e as Error).message}`); } } // Usage try { const config = loadConfig("config.json"); console.log(`Server running on ${config.host}:${config.port}`); } catch (err) { if (err instanceof ConfigError) { console.error("Config error:", err.message); process.exit(1); } }
After (Go):
package main import ( "encoding/json" "fmt" "os" ) type Config struct { Host string `json:"host"` Port int `json:"port"` Debug bool `json:"debug"` } type ConfigError struct { Message string } func (e *ConfigError) Error() string { return e.Message } func LoadConfig(path string) (*Config, error) { if _, err := os.Stat(path); os.IsNotExist(err) { return nil, &ConfigError{ Message: fmt.Sprintf("config file not found: %s", path), } } content, err := os.ReadFile(path) if err != nil { return nil, &ConfigError{ Message: fmt.Sprintf("failed to read config: %v", err), } } var config Config if err := json.Unmarshal(content, &config); err != nil { return nil, &ConfigError{ Message: fmt.Sprintf("failed to parse config: %v", err), } } if config.Host == "" || config.Port == 0 { return nil, &ConfigError{ Message: "invalid config format", } } return &config, nil } func main() { config, err := LoadConfig("config.json") if err != nil { var configErr *ConfigError if errors.As(err, &configErr) { fmt.Fprintf(os.Stderr, "Config error: %s\n", configErr.Message) os.Exit(1) } } fmt.Printf("Server running on %s:%d\n", config.Host, config.Port) }
Example 3: Complex - HTTP API with Async Operations
Before (TypeScript):
import axios from 'axios'; import { EventEmitter } from 'events'; interface User { id: string; name: string; email: string; } interface Post { id: string; userId: string; title: string; content: string; } class APIClient extends EventEmitter { private baseURL: string; private cache: Map<string, any> = new Map(); constructor(baseURL: string) { super(); this.baseURL = baseURL; } async fetchUser(id: string): Promise<User> { const cacheKey = `user:${id}`; if (this.cache.has(cacheKey)) { this.emit('cache-hit', cacheKey); return this.cache.get(cacheKey); } try { const response = await axios.get<User>(`${this.baseURL}/users/${id}`); this.cache.set(cacheKey, response.data); this.emit('user-fetched', response.data); return response.data; } catch (error) { this.emit('error', error); throw new Error(`Failed to fetch user ${id}: ${error.message}`); } } async fetchUserPosts(userId: string): Promise<Post[]> { try { const response = await axios.get<Post[]>( `${this.baseURL}/users/${userId}/posts` ); return response.data; } catch (error) { throw new Error(`Failed to fetch posts: ${error.message}`); } } async getUserWithPosts(userId: string): Promise<{ user: User; posts: Post[] }> { // Fetch in parallel const [user, posts] = await Promise.all([ this.fetchUser(userId), this.fetchUserPosts(userId), ]); return { user, posts }; } clearCache(): void { this.cache.clear(); this.emit('cache-cleared'); } } // Usage async function main() { const client = new APIClient('https://api.example.com'); client.on('user-fetched', (user: User) => { console.log('Fetched user:', user.name); }); client.on('cache-hit', (key: string) => { console.log('Cache hit:', key); }); client.on('error', (error: Error) => { console.error('API error:', error.message); }); try { const result = await client.getUserWithPosts('123'); console.log(`${result.user.name} has ${result.posts.length} posts`); // Second fetch will hit cache await client.fetchUser('123'); } catch (error) { console.error('Failed:', error.message); process.exit(1); } } main();
After (Go):
package main import ( "encoding/json" "fmt" "net/http" "sync" ) type User struct { ID string `json:"id"` Name string `json:"name"` Email string `json:"email"` } type Post struct { ID string `json:"id"` UserID string `json:"userId"` Title string `json:"title"` Content string `json:"content"` } type Event struct { Type string Data interface{} } type APIClient struct { baseURL string cache map[string]interface{} cacheMu sync.RWMutex eventCh chan Event httpClient *http.Client } func NewAPIClient(baseURL string) *APIClient { return &APIClient{ baseURL: baseURL, cache: make(map[string]interface{}), eventCh: make(chan Event, 100), httpClient: &http.Client{}, } } func (c *APIClient) Events() <-chan Event { return c.eventCh } func (c *APIClient) emit(eventType string, data interface{}) { select { case c.eventCh <- Event{Type: eventType, Data: data}: default: // Don't block if no listeners } } func (c *APIClient) FetchUser(id string) (*User, error) { cacheKey := fmt.Sprintf("user:%s", id) // Check cache c.cacheMu.RLock() if cached, ok := c.cache[cacheKey]; ok { c.cacheMu.RUnlock() c.emit("cache-hit", cacheKey) return cached.(*User), nil } c.cacheMu.RUnlock() // Fetch from API url := fmt.Sprintf("%s/users/%s", c.baseURL, id) resp, err := c.httpClient.Get(url) if err != nil { c.emit("error", err) return nil, fmt.Errorf("failed to fetch user %s: %w", id, err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { err := fmt.Errorf("API returned status %d", resp.StatusCode) c.emit("error", err) return nil, err } var user User if err := json.NewDecoder(resp.Body).Decode(&user); err != nil { c.emit("error", err) return nil, fmt.Errorf("failed to decode user: %w", err) } // Update cache c.cacheMu.Lock() c.cache[cacheKey] = &user c.cacheMu.Unlock() c.emit("user-fetched", &user) return &user, nil } func (c *APIClient) FetchUserPosts(userID string) ([]Post, error) { url := fmt.Sprintf("%s/users/%s/posts", c.baseURL, userID) resp, err := c.httpClient.Get(url) if err != nil { return nil, fmt.Errorf("failed to fetch posts: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("API returned status %d", resp.StatusCode) } var posts []Post if err := json.NewDecoder(resp.Body).Decode(&posts); err != nil { return nil, fmt.Errorf("failed to decode posts: %w", err) } return posts, nil } type UserWithPosts struct { User *User Posts []Post } func (c *APIClient) GetUserWithPosts(userID string) (*UserWithPosts, error) { // Fetch in parallel using goroutines type userResult struct { user *User err error } type postsResult struct { posts []Post err error } userCh := make(chan userResult, 1) postsCh := make(chan postsResult, 1) go func() { user, err := c.FetchUser(userID) userCh <- userResult{user, err} }() go func() { posts, err := c.FetchUserPosts(userID) postsCh <- postsResult{posts, err} }() // Wait for both userRes := <-userCh postsRes := <-postsCh if userRes.err != nil { return nil, userRes.err } if postsRes.err != nil { return nil, postsRes.err } return &UserWithPosts{ User: userRes.user, Posts: postsRes.posts, }, nil } func (c *APIClient) ClearCache() { c.cacheMu.Lock() defer c.cacheMu.Unlock() c.cache = make(map[string]interface{}) c.emit("cache-cleared", nil) } func main() { client := NewAPIClient("https://api.example.com") // Event listener goroutine go func() { for event := range client.Events() { switch event.Type { case "user-fetched": user := event.Data.(*User) fmt.Println("Fetched user:", user.Name) case "cache-hit": key := event.Data.(string) fmt.Println("Cache hit:", key) case "error": err := event.Data.(error) fmt.Println("API error:", err) case "cache-cleared": fmt.Println("Cache cleared") } } }() result, err := client.GetUserWithPosts("123") if err != nil { fmt.Fprintf(os.Stderr, "Failed: %v\n", err) os.Exit(1) } fmt.Printf("%s has %d posts\n", result.User.Name, len(result.Posts)) // Second fetch will hit cache _, _ = client.FetchUser("123") }
See Also
For more examples and patterns, see:
- Foundational patterns with cross-language examplesmeta-convert-dev
- TypeScript development patternslang-typescript-dev
- Go development patternslang-golang-dev