Go and Rust dominate modern backend conversations for good reason: Go compiles in seconds, ships static binaries, and its goroutine scheduler makes concurrency approachable. Rust eliminates data races at compile time and delivers predictable performance without a garbage collector. But LLMs often produce mediocre code in both languages unless you write precise prompts. A vague "write a Go server" yields toy code with no error handling, no context cancellation, and no benchmarks. A well-structured prompt produces production-ready code with tests and profiling hooks.
This guide provides 12 concrete, copy-paste-ready prompts for Go and Rust code generation, refactoring, and optimization. Each includes a usage example and a note on the task it solves. Use them with any capable LLM (GPT-4, Claude, Gemini, or open-weight models like DeepSeek-Coder and Qwen2.5-Coder). The prompts assume you know your domain; they just make the model work at your level.
1. Goroutines and Channels: Worker Pool with Graceful Shutdown
Task: Generate a bounded worker pool that processes jobs concurrently, respects context cancellation, and shuts down without leaking goroutines.
Prompt:
You are a senior Go engineer. Write a worker pool with these requirements:
- N workers read from a jobs channel (chan Job)
- Results go to a results channel (chan Result)
- Respect context.Context cancellation: when ctx is done, workers stop accepting new jobs
- Use sync.WaitGroup to wait for all workers
- The pool exposes Submit(job Job) bool and Close()
- No goroutine leaks: verify with a test using runtime.NumGoroutine()
Provide the full file, a main.go demo, and a test file. Explain why we close the jobs channel before waiting.
Example: For an image-resizing service processing 10k uploads/hour, this prompt yields a pool with backpressure via a buffered channel and safe shutdown on SIGTERM. The generated test typically asserts goroutine count returns to baseline after Close().
Why it works: It names the exact primitives (WaitGroup, context) and demands a leak test. LLMs default to naive go func() loops; this forces discipline.
2. Refactoring a Python Script to Idiomatic Go
Task: Migrate a small Python service (e.g., a Flask endpoint doing DB queries and JSON serialization) to Go.
Prompt:
Convert this Python Flask service to idiomatic Go using net/http (no frameworks).
Requirements:
- Keep the same routes and JSON response shapes
- Use database/sql with prepared statements
- Add context timeouts (5s) on DB calls
- Return proper HTTP status codes (400 for bad input, 500 for internal errors)
- Include a Makefile with build, test, and run targets
Python code:
<paste code>
Example: A 200-line Flask app with three endpoints becomes a ~300-line Go service with http.Server{ReadTimeout, WriteTimeout} configured. The prompt prevents the model from importing Gin or Echo unless you want it.
Why it works: Specifying "no frameworks" and exact status codes avoids the common LLM habit of adding dependencies. Requesting a Makefile makes the output immediately runnable.
3. Borrow Checker Errors: Explain and Fix
Task: Diagnose a Rust compile error involving lifetimes or mutable aliasing.
Prompt:
You are a Rust expert. The compiler reports:
<paste full error including E-code>
Here is the relevant code:
<paste code>
1. Explain the error in plain English.
2. Show the minimal fix.
3. Show an alternative using Rc<RefCell<T>> or Arc<Mutex<T>> and explain when each is appropriate.
4. Add a unit test proving the fix compiles and runs.
Example: A common error is cannot borrow 'x' as mutable more than once at a time when iterating and mutating a Vec. The model will suggest retain or index-based iteration, plus a test.
Why it works: Asking for both minimal and alternative fixes teaches the underlying ownership model rather than just patching.
4. Async/Await in Rust with Tokio
Task: Build a concurrent HTTP client that fetches multiple URLs with a timeout and returns results in order.
Prompt:
Write a Rust async program using tokio and reqwest.
Requirements:
- Fetch a list of URLs concurrently with a max of 10 in flight (use futures::stream::iter + buffer_unordered)
- Per-request timeout of 3 seconds
- Collect status codes and body lengths
- Return results in the original order
- Include Cargo.toml with pinned versions and a main that reads URLs from args
Explain the difference between join_all and buffer_unordered.
Example: This pattern is standard for web scrapers and health-check tools. The prompt yields code using tokio::time::timeout and FuturesUnordered, with a clear explanation of backpressure.
Why it works: Naming buffer_unordered and the order requirement pushes the model past the naive join_all approach that ignores rate limits.
5. Benchmarking Go Code with testing.B
Task: Write benchmarks for a hot function and interpret the results.
Prompt:
Write Go benchmarks for this function using testing.B:
<paste function>
Requirements:
- Benchmark with b.ReportAllocs()
- Include a sub-benchmark for input sizes 100, 1000, 10000
- Use b.RunParallel for a concurrent variant
- Show how to run with -benchmem and interpret ns/op and B/op
- Suggest one optimization based on the likely allocation pattern
Example: For a JSON parsing function, the benchmark reveals allocations from []byte conversions. The model suggests json.Decoder with a reused buffer, often cutting B/op significantly.
Why it works: b.ReportAllocs() and -benchmem are concrete flags; the model produces runnable commands like go test -bench=. -benchmem.
6. Profiling Rust with perf and Flamegraph
Task: Identify CPU hotspots in a Rust binary and generate a flamegraph.
Prompt:
My Rust binary is CPU-bound. Give me a step-by-step workflow to profile it on Linux:
- Build with debug symbols in release mode (Cargo.toml settings)
- Run perf record with the right flags
- Generate a flamegraph using cargo-flamegraph
- Interpret the output and identify the top 3 functions
Include exact commands and a sample Cargo.toml [profile.release] section.
Example: The output typically includes [profile.release] debug = true and perf record -g --call-graph dwarf ./target/release/app. This is the standard workflow documented by the Rust Performance Book.
Why it works: Asking for exact commands and profile settings avoids vague advice. The model references real tools (perf, cargo-flamegraph).
7. Migrating a Python Microservice to Go: Step-by-Step Plan
Task: Plan and execute a migration of a Python service to Go without downtime.
Prompt:
Create a migration plan from Python (FastAPI) to Go for a service with 5 endpoints and a PostgreSQL database.
Include:
- Strangler Fig pattern: run both services behind a reverse proxy, shift traffic gradually
- Shared schema: use the same migrations, no DB changes
- Contract tests to compare responses between Python and Go
- Rollback strategy
- A checklist for the first week in production
Output as a Markdown table with phases, tasks, and success criteria.
Example: Teams often use Nginx or Envoy to split traffic by percentage. The prompt yields a phased plan with measurable criteria like "p99 latency within 10% of Python baseline."
Why it works: Naming the Strangler Fig pattern and contract tests forces a realistic plan instead of a big-bang rewrite.
8. Error Handling in Go: Wrapping and Sentinels
Task: Refactor error handling to use errors.Is, errors.As, and %w.
Prompt:
Refactor this Go code to use idiomatic error handling:
<paste code>
Requirements:
- Wrap errors with fmt.Errorf("...: %w", err)
- Define sentinel errors with errors.New for known cases
- Use errors.Is for comparisons and errors.As for typed errors
- Add a custom error type with Unwrap()
- Show how to log with slog while preserving the error chain
Example: A DB layer returns ErrNotFound; the handler uses errors.Is(err, ErrNotFound) to return 404. This is the pattern recommended in the Go blog post "Working with Errors in Go 1.13."
Why it works: It references real APIs and a specific Go version, so the model produces correct, modern code.
9. Rust Ownership: Converting a Tree Structure
Task: Implement a tree with parent pointers without fighting the borrow checker.
Prompt:
Implement a tree in Rust where nodes need parent references.
Compare three approaches:
1. Arena with indices (Vec<Node> + usize)
2. Rc<RefCell<Node>> with Weak for parent
3. Indices with a generational arena crate (e.g., slotmap)
Provide code for each, and explain trade-offs in terms of performance, safety, and ergonomics. Recommend one for a read-heavy workload.
Example: The arena approach is common in parsers and compilers. The model will show struct Node { parent: Option<usize>, children: Vec<usize> } and explain why it avoids RefCell runtime checks.
Why it works: Comparing approaches with explicit trade-offs produces a decision framework, not just code.
10. Concurrency Safety: Detecting Data Races
Task: Find and fix data races in Go code using the race detector.
Prompt:
Analyze this Go code for data races:
<paste code>
1. Identify all shared mutable state.
2. Show how to run the race detector: go test -race ./...
3. Propose fixes using sync.Mutex, atomic, or channels.
4. Write a test that fails with -race before the fix and passes after.
Example: A common bug is incrementing a counter from multiple goroutines. The model suggests atomic.AddInt64 or a mutex, and provides a test that reliably triggers the race detector.
Why it works: The -race flag is a real, powerful tool; requiring a failing-then-passing test makes the fix verifiable.
11. Cross-Compilation and Static Binaries
Task: Build Go binaries for Linux, macOS, and Windows with CGO disabled.
Prompt:
Write a Makefile that cross-compiles a Go program for linux/amd64, linux/arm64, darwin/arm64, and windows/amd64.
Requirements:
- CGO_ENABLED=0 for static binaries
- Output to dist/ with clear naming
- Include a target for Docker multi-stage build using scratch or distroless
- Show how to verify the binary is static with file and ldd
Example: The resulting Dockerfile uses FROM gcr.io/distroless/static for a tiny image. This is standard practice for Go services.
Why it works: It specifies real base images and verification commands, producing deployable artifacts.
12. Testing Rust with Property-Based Tests
Task: Add property-based tests to a Rust function using proptest.
Prompt:
Write property-based tests for this Rust function using proptest:
<paste function>
Requirements:
- Define invariants (e.g., round-trip encode/decode, idempotence)
- Use proptest! macro with strategies for strings, integers, and vectors
- Include a regression test for a known edge case
- Explain how proptest shrinks failing inputs
Example: For a custom serialization format, the round-trip property catches edge cases like empty strings and Unicode. Proptest then minimizes the failing case automatically.
Why it works: Property-based testing is underused; naming proptest and invariants yields high-value tests beyond example-based cases.
| Prompt # | Focus | Language | Key Tool/API |
|---|---|---|---|
| 1 | Worker pool | Go | context, WaitGroup |
| 2 | Python to Go | Go | net/http, database/sql |
| 3 | Borrow checker | Rust | lifetimes, Rc/RefCell |
| 4 | Async/await | Rust | tokio, reqwest |
| 5 | Benchmarking | Go | testing.B, -benchmem |
| 6 | Profiling | Rust | perf, cargo-flamegraph |
| 7 | Migration plan | Go | Strangler Fig |
| 8 | Error handling | Go | errors.Is/As, %w |
| 9 | Ownership patterns | Rust | arena, RefCell |
| 10 | Data races | Go | -race detector |
| 11 | Cross-compilation | Go | CGO_ENABLED=0 |
| 12 | Property tests | Rust | proptest |
These prompts are starting points. The more context you provide—your actual code, error messages, and performance targets—the better the output. For deeper structured learning on Go, Rust, and AI-assisted development, explore courses at asibiont.com. If you want to go further, try combining prompts: ask the model to benchmark the code it just generated, then profile it. That loop—generate, measure, optimize—is how you turn LLM output into production systems.
Comments