为什么我选择Go,而不是Rust或Python
Why I Vibe in Go, Not Rust or Python

原始链接: https://lifelog.my/episode/why-i-vibe-in-go-not-rust-or-python

## 在人工智能驱动的世界中,为“乏味”的Go语言辩护 作者最近利用Go语言,并在人工智能代码生成的辅助下,在一个会话中构建了一个功能齐全的博客——包括域名路由和音频播放器等复杂功能。这次经历突出了为什么,尽管人们认为Go语言“乏味”,但它是与人工智能合作以实现快速开发和可靠部署的理想语言。 与缺乏强大编译器的Python不同,Python容易让错误滑入生产环境,而Go语言的编译器会强制执行结构并尽早发现问题。虽然Rust优先考虑正确性,并使用借用检查器,但它可能会通过迫使人类关注低级细节而不是整体架构来阻碍人工智能驱动的开发。 Go语言取得了一种平衡。它采用五层方法——编译器、类型系统、显式错误处理、强制的简洁性和人工监督——有效地过滤代码。编译器处理基本错误,类型系统捕获结构性问题,而强制的简洁性则便于人工审查人工智能生成的代码。 至关重要的是,Go语言生成一个单一的可移植二进制文件,与Python的依赖管理或Rust冗长的构建过程相比,大大简化了部署。对于作者来说,Go语言并非关于成为*最好*的语言,而是成为最好的*过滤器*——捕获关键错误,并让人类专注于高级设计和改进。

一个 Hacker News 的讨论围绕着一位开发者(“riclib”)解释了他在使用 AI 辅助下,用于“氛围编码”(快速构建用于娱乐和探索的项目)时对 Go 语言的偏好。Riclib 最近用七次提交和零次测试失败构建了一个完整的博客,强调了 Go 语言在生成单个可部署二进制文件方面的简洁性和效率。 虽然承认 Python 的快速上手和 Rust 的安全性,但作者认为 Go 语言“乏味”是一种积极的方面,允许专注于架构和期望的结果,而不是语言的复杂性。 评论者们争论了 Go 语言与其他语言(如 Rust、Python、Java 和 TypeScript/Node.js)的优缺点。一些人质疑了网站是由 Go 语言构建的说法,因为可以看到 HTML、CSS 和 JavaScript,并指出可以通过缓存进行潜在的性能改进。 另一些人讨论了使用 AI 进行代码生成的好处,以及代码审查和贡献的重要性。
相关文章

原文

Last night I built a website from scratch. Not a landing page. A full blog with three-domain routing, animated video covers, an audio player with playlists, dark mode, RSS feeds, social cards, and a sticky sidebar with a lightbox. Seven commits. Zero test failures. One binary.

The site you’re reading this on. Built in one session. In Go.

I work with an AI that writes most of the code. The question everyone asks is which language to vibe in. Python is fast to start. Rust is correct by construction. Go is boring.

I choose boring. Here’s why.

The Case Against Python

Python has no compiler. It has type hints, which are optional, which means they’re not there.

When an AI writes four thousand lines of Python in a day, every line runs. Every line produces output. And somewhere in those four thousand lines, a dictionary key is misspelled, a None propagates through three function calls before it surfaces, a variable changes type between assignment and use because nothing prevents it.

The bug arrives in production. Not because the AI is bad. Because nothing between the AI and production said no.

When the machine writes 90% of the code, “it runs” is not a quality bar. It’s the absence of one.

Mypy exists. Mypy is optional. Optional means it’s not there. I’ve never seen a Python project where mypy covers 100% of the code with strict mode. I’ve seen hundreds where it covers the 20% someone added last quarter. The other 80% is Any, all the way down.

Python is fast to prototype. It’s also fast to production-incident. These are the same property.

The Case Against Rust

The case against Rust is more interesting because Rust is correct.

The borrow checker catches real bugs. The type system is rigorous. The compiler says no, and when it says no, it’s right. The problem is that the compiler says no too much.

When an AI writes Rust, the borrow checker fights the generated code. I — the human — spend my taste budget on lifetime annotations instead of architecture. Instead of saying “why do we keep both around?” — five words that collapse complexity — I’m saying “add a .clone() here” or “wrap this in Arc<Mutex<>>.” I’m using Layer 5, the most expensive layer, on problems the language invented.

The Async Tax

Go has goroutines. You write go func() and it works.

Rust has colored functions. You pick tokio as your async runtime. Then every crate you choose must be tokio-compatible. One crate uses async-std. Now you have two runtimes. The AI doesn’t know which one to use. You spend Layer 5 explaining concurrency runtimes instead of designing the system.

Last night my binary ran HTTP handlers, NATS consumers, filesystem watchers, git push timers, and SSE streams. All goroutines. All started with go func(). No runtime selection. No colored functions. No Pin<Box<dyn Future>>. No Arc<Mutex<>> wrappers around state that is just a struct field.

In Rust, each of those would require tokio compatibility verification. The NATS client — is it tokio-native? The filesystem watcher — does it use async-std internally? The timer — does it spawn a thread or use the tokio runtime? These are real questions that consume real human attention. In Go, they don’t exist.

The Ecosystem Roulette

Come back to a Rust project after three months. Run cargo update. Half your transitive dependencies don’t compile because a trait bound changed in a crate you’ve never heard of.

The language is correct. The ecosystem is fragile.

Go’s compatibility promise is real. go build today, go build in two years. Same binary. Same behavior. The standard library doesn’t break. The dependencies you chose in 2024 still compile in 2026 because Go takes backwards compatibility seriously at every level — language, standard library, and ecosystem culture.

Rust optimizes for correctness at the cost of velocity. When vibing, velocity is the point.

The Case For Go

Last night I added a field to a struct. SiteLinks needed a Riclib field and a FeedRiclib field. I added them. Ran go build. The compiler showed me every call site that needed updating. Not some of them. Every single one. I fixed them. Built again. Zero errors.

In Python, the struct is a dictionary. Adding a field changes nothing. The missing key surfaces at runtime, on the code path the tests didn’t cover, in production, at 2 AM.

In Rust, the struct has the field. The compiler catches the call sites. But it also catches the lifetime of the string reference in the field, the borrow of the struct across a thread boundary, and the async Send bound that the new field violates. Four problems. One was mine. Three were the language’s.

In Go, the compiler caught my problem and only my problem. Then it got out of the way.

The Five Layers

Five layers between the machine and production:

Layer 1: The Compiler. Catches the obvious. Types don’t match. Imports unused. Variables shadowed. The machine’s first draft fails here. This is cheap. This is instant. This is the floor.

Layer 2: The Type System. Catches the structural. A function expects WikiLinkResolver, not func(string) string. A handler returns ([]byte, error), not just []byte. The machine’s second draft fails here, sometimes.

Layer 3: Explicit Errors. Catches the ignored. Every error must be handled. Not caught, not swallowed — handled. if err != nil. Four hundred times per file. The machine cannot write except: pass in Go because Go does not have except. The machine must handle the error. The human can see the handling.

Layer 4: Enforced Simplicity. Catches nothing. Prevents everything. One way to loop. One way to format. One way to organize. The machine generates uniform code because Go does not permit non-uniform code. The human can read it at a glance. The human can steer.

Layer 5: The Human. Catches the subtle. “Make it dark mode aware.” “The cover is cut at the top.” “The stickiness didn’t work.” Five-word corrections that no compiler can generate because five words require taste, and taste requires understanding what you’re building and why.

The human is the most expensive layer. Go makes sure the human only handles what four cheaper layers already missed. Python sends everything to the human. Rust sends the human’s problems plus its own.

The Binary Argument

When the session was done, the result was one file. I ran scp to copy it to the server. The server ran it. Three domains — lifelog.my, yagnipedia.com, riclib.com — served from one binary. No runtime. No dependency installation. No container.

The deploy script is 30 lines of bash:

GOOS=linux GOARCH=amd64 go build -o lg-linux .
scp lg-linux server:/home/lifelog/bin/lg
ssh server "systemctl restart lifelog"

That’s the deployment.

Python needs a virtualenv, or a Docker container, or both. The Dockerfile installs system dependencies, copies requirements.txt, runs pip install, copies the code, sets the entrypoint. The Go equivalent is COPY binary /usr/local/bin/. One line. One layer. One file.

Rust produces a binary too. But cargo build downloads 400 crates first. The build takes four minutes. The CI pipeline has a crate cache that breaks every time a dependency updates. The binary is correct. Getting to the binary is the tax.

The site you are reading this essay on was built last night. In one session.

The AI wrote the templates, the HTTP handlers, the CSS, the audio player, the RSS feed, the social card meta tags, the lightbox, the sticky sidebar. I wrote five-word corrections. The compiler caught the structural errors. The type system caught the interface mismatches. The explicit error handling ensured nothing failed silently. The enforced simplicity meant I could read the AI’s code at a glance, because Go code only looks one way.

The feedback loop: templ generate && go build. If it compiles, it works. If it doesn’t compile, the error message is four lines, not four paragraphs. The AI reads the error, fixes the code, builds again. Seconds, not minutes.

Seven commits. Zero test failures. The binary shipped at 2 AM. It’s still running.

The Filter

I don’t vibe in Go because Go is the best language. I vibe in Go because Go is the best filter.

When the machine writes 90% of the code, the language is not a tool for writing. It’s a tool for catching. Python catches nothing. Rust catches everything, including things that aren’t problems. Go catches the things that matter and gets out of the way.

The compiler is the floor. The human is the taste. The binary is the proof.

See Also

  • Vibe In Go — The original Yagnipedia entry: five layers, 559,872 paths, the compiler is the bouncer
  • Vibe Engineering — Steering the machine with five-word corrections across half a million design paths
  • Vibe Coding — What happens when the machine writes code in a language with no compiler
  • AI in Go — The ecosystem argument: net/http is the ecosystem
  • Boring Technology — One way to do things is boring. Boring is navigable.
联系我们 contact @ memedata.com