从Rust到Ruby
摘要
开发人员描述使用LLM将一个15,000行的Rust Web应用转换为Ruby on Rails,发现Ruby版本明显更短,并评估了开发速度、安全性和可测试性方面的权衡。
暂无内容
查看缓存全文
缓存时间: 2026/05/27 03:59
# 从 Rust 到 Ruby
来源:https://xlii.space/eng/from-rust-to-ruby/ 2026\-05\-26
谁会干这种事?!显然:是我。我有个用 Rust 写的个人项目。不算大,总共大概 3 万行代码。Rust 很啰嗦,所以其实也没多厉害。我把它搁置了一段时间,转而玩本地推理、LLM、写 Agent,然后我的注意力被 Ruby 吸引了。已经好一阵子了。所以我得四处看看,提醒自己 Ruby 和 Ruby on Rails 现在在做什么。它们做得相当不错。有一些类型方面的尝试(Sorbet),语言本身一如既往地简洁。然后我就有了这个想法……但首先得做个介绍:
在我的 Rust 应用里,有一个独立的 crate,基本上是一个用 Tera 和 Axum 写的 web 应用。总共 14,943 行 Rust 代码,编译时间大约 10 秒(可能代码不大,但它拖拽了整个宇宙作为依赖),然后是非常重的端到端测试,涉及启动 Playwright 以及(因为几乎无法 mock)一个独立的数据库命名空间和 mock 服务(还有一个非常特殊的 internal-api crate,允许 Playwright 以无头模式与应用交互……)。所以我想:“嗯,不知道能不能让我的本地 Qwen3.6 做一次一次性转换”。但在此之前,我先做了调研。我问了几个实例,分析这个项目在复杂性、稳定性、可测试性等方面的得失,虽然(显然)稳定性会下降(Ruby 没有类型),但也没那么糟糕(Sorbet 在 Ruby 里有类型!)。
```
┌─────────────────────────────────┬──────────────────┬───────┬────────────────┐
│ 领域 │ Rust/Axum/Diesel │ Rails │ Rails + Sorbet │
├─────────────────────────────────┼──────────────────┼───────┼────────────────┤
│ 适合单人开发 │ 60 │ 90 │ 85 │
├─────────────────────────────────┼──────────────────┼───────┼────────────────┤
│ 开发速度 │ 40 │ 90 │ 75 │
├─────────────────────────────────┼──────────────────┼───────┼────────────────┤
│ 安全性 │ 95 │ 55 │ 80 │
├─────────────────────────────────┼──────────────────┼───────┼────────────────┤
│ 开发复杂度 │ 70 │ 90 │ 75 │
├─────────────────────────────────┼──────────────────┼───────┼────────────────┤
│ 性能 │ 95 │ 50 │ 50 │
├─────────────────────────────────┼──────────────────┼───────┼────────────────┤
│ 样板代码 │ 30 │ 85 │ 80 │
├─────────────────────────────────┼──────────────────┼───────┼────────────────┤
│ 端到端测试可测试性 │ 40 │ 75 │ 75 │
├─────────────────────────────────┼──────────────────┼───────┼────────────────┤
│ 单元测试可测试性 │ 20 │ 90 │ 90 │
├─────────────────────────────────┼──────────────────┼───────┼────────────────┤
│ 集成测试可测试性 │ 30 │ 85 │ 85 │
├─────────────────────────────────┼──────────────────┼───────┼────────────────┤
│ 总和 │ 480 │ 710 │ 695 │
└─────────────────────────────────┴──────────────────┴───────┴────────────────┘
```
所以最后看起来,如果这个应用是 Ruby on Rails 写的,我的结果会好 *(舔手指,转向风)* 1.47 倍。有意思。

我本地有台 4090 Ti(AI 热潮前买的用来玩游戏)上跑着 LLM1 — 我是个自由人,拥有无限 token2。所以我想:放马过来!因为这个项目比较小,转换花了大约 30 分钟。我不知道它能不能运行,因为我还没试过。但我检查了一件事,然后惊恐地盯着:
```
$ fd . -e rs -uu | xargs cat | wc -l
14943
$ fd . -e rb -uu | xargs cat | wc -l
3322
```

没错,各位!代码行数减少了 77%;每行 Ruby 代码对应 4.49 行 Rust 代码。我浏览了 Ruby 代码,看起来……还行。可能有一些 bug(没有兔子),但就我过时的眼光来看,代码干净且符合惯用写法。我准备进一步检查,带着以下几点想法:
- 我可以使用 Agents 添加类型,所以类型安全可能得到缓解
- Ruby/Rails 基本上是电池加厨房水槽全包,这比 3GiB 的编译依赖强多了
- 测试会*容易得多*
```
VCR.use_cassette("llm_call") do
result = LlmClient.match(entry, data_list)
expect(result.results.size).to eq(data_list.size)
end
```
对比
```
#[derive(Debug)]
pub struct MockProvider {
responses: Arc<RwLock<Vec<Response>>>,
call_count: Arc<AtomicUsize>,
}
impl Default for MockProvider {
fn default() -> Self {
Self {
responses: Arc::new(RwLock::new(vec![Response::default()])),
call_count: Arc::new(AtomicUsize::new(0)),
}
}
}
impl MockProvider {
pub fn new(responses: Vec<Response>) -> Self {
Self {
responses: Arc::new(RwLock::new(responses)),
call_count: Arc::new(AtomicUsize::new(0)),
}
}
}
#[async_trait]
impl Provider for MockProvider {
async fn match(&self, entry: &Entry, data_list: &[Data]) -> Result<MatchResult> {
self.call_count.fetch_add(1, Ordering::SeqCst);
let responses = self.responses.read().await;
Ok(MatchResult {
results: responses.clone()
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_mock_provider_returns_expected_results() {
let expected = vec![Response::default()];
let provider = MockProvider::new(expected.clone());
let result = provider.match(&Entry::default(), &[]).await.unwrap();
assert_eq!(result.results, expected);
assert_eq!(provider.call_count.load(Ordering::SeqCst), 1);
}
}
```
……你懂那个*氛围*,对吧?做自己项目的好处是,你可以做疯狂的决策,而我会*非常仔细地*审视这个决定。
相似文章
从Go迁移到Rust
一份为Go开发者迁移到Rust编写的全面指南,专注于后端服务,对比正确性、运行时和人体工程学方面的权衡,并提供关于渐进式迁移的实用建议。
@alphabatcher:54万行Rails代码是进入智能体时代的残酷方式 Garry's List 发布时包含:> 26.2万行应用代码 > …
对AI智能体时代大型Rails代码库的批评,提出转向基于技能的开发方式,使用智能体、Markdown技能和TypeScript实现确定性I/O。
为什么多年来 Ruby 依然让人有家的感觉
作者回顾了使用 Ruby 的 15 年经历,称赞了其隐藏特性,如 refinements、delegation 以及新的 ZJIT JIT 编译器,并指出 Ruby 搭配 ZJIT 正在缩小与 Go 和 Rust 等更快语言的性能差距。
一夜之间格式化2500万行代码库:rubyfmt的故事
Stripe工程师分享了他们使用rubyfmt在一夜之间格式化2500万行Ruby代码库的经验,重点介绍了开发者生产力的提升。
我们如何(及为何)将生产环境的C++前端基础设施重写为Rust
NearlyFreeSpeech.NET 将其生产环境的C++前端基础设施(nfsncore)重写为Rust,该系统负责所有传入请求的路由、缓存和访问控制。迁移的动机是Rust的安全性保证、性能、生态系统优势以及老化的C++代码库的局限性。