当前位置: 欣欣网 > 码农

为什么一周后我就快放弃 Rust 了

2024-09-19码农

我在夏天慢慢地读完了【Rust 编程语言】,并做了大量的笔记。上周我开始用它写点东西,一周后我觉得我已经看到了足够多的东西。我可能会放弃我投入的成本,因为我更看重我的时间和理智。

我原本以为借用检查会是「问题」,但我很容易理解这些概念。你可以传递引用,或者克隆它们。我在 C# 中编写过低延迟交易系统,对多线程很熟悉,所以高级智能指针对我来说很容易理解。

但这些都不是问题。

书中提到了泛型,在书面文字中看起来还不错,但当我开始使用一个 web 框架时,这是一个很好的方法来提前了解开发体验,我发现自己陷入了许多 TypeScript 开发者为自己和他们可怜的同事制造的糟糕的类型系统泥潭中。

我找不到合适的词语来解释,但 Rust 实际上与 TypeScript 非常相似,它的类型系统「非常强大」,以至于它最终主导了代码的编写方式。

但它比 TypeScript 更糟糕,因为它还拥有一个半生不熟的异步系统,以及生命周期边界、一堆小包装的智能指针类型,以及各种巧妙的功能,这些功能变得难以理解。你无法逆向工程解决问题,因为存在着多层复杂和叠加的类型推断,甚至包括被称为关联类型的元素,每个元素都有自己的边界和约束。

但正是编译器进行的层层推断,人类无法做到,至少我无法做到,除非忍受真正的头痛和悲伤。当它工作正常,或者是你自己编写的代码时,它很好用,但一旦你偏离框架文档,你就完蛋了。

它就像一个魔方,对于它的大多数应用来说,它带来的痛苦并不值得。

我将再次尝试 Go。我读过一本书,但没有项目可以使用它。我喜欢 Go 几乎没有功能、几乎没有改变或添加功能、只有一个做事方式、速度非常快,并且在某些测试中几乎与 Rust 一样快。此外,一些优秀的公司使用它——使用某种语言的公司很重要。

在超过 20 年的编码生涯中,我意识到简单性和生产力才是最重要的,它们会带来深切的快乐。Rust 绝对不简单,而且除了替换更糟糕的语言之外,不值得为它付出生产力的代价。

我读过一些对 Rust 的评论,那些评论来自将数年时间投入到 Rust 中的人,我在寻找「它太棒了」这样的断言,但我看到的都是褒贬不一的评价。这可不是什么好兆头。我认为这么多人喜欢 Rust 的原因可能是他们从哪里迁移过来,以及他们可能在其他语言中使用了不好的实践。

Rust 在某些类型上依赖于返回结果,而这些类型只是你可以在任何语言中使用的模式。

你知道,使用一个编译的、类型安全的语言,拥有基本的泛型,并且永远不会创建空值,可以让你走得很远。在我的经验中,大多数问题都源于返回空值或使用空值实例化结构体的单一错误。第二组头痛问题来自没有尽早失败,导致你最终得到的跟踪信息距离问题的根源很远。

实际上,这两件事构成了编码的大部分痛苦。

编辑

由于这篇文章引起了关注和反驳,下面是一个问题的示例。请记住,我已经读过这本书,从每一页中手写了笔记,然后将这些笔记输入到我的 GitHub 笔记中。实际上,我也完成了书中的编码教程。

但同时,我也在使用强类型语言编码了 25 年。我编码了将近 40 年,我的智商是 150。

我花了大约 2 个小时只是盯着下面的代码,试图理解它以及它对我作为框架用户的意义,并尽量避免过多地依赖 AI 来帮助我,因为我需要能够训练自己几乎瞬间理解这种东西及其含义。

我可以从逻辑上把它分解,但我不想使用一种允许人们编写「戈尔迪之结」的语言,或者在一个可能出现「戈尔迪之结」的项目中工作,比如一个完整的应用程序。对于 TypeScript 代码来说,情况也是如此。我确信 Rust 非常适合小型组件。

当我编写一个使用比这种方法更简单的方法来实现类似中间件的功能的方法时,我改变了一些东西,我现在不记得是什么了,但它似乎改变了返回值类型,并且无法从编译错误、我的代码或框架的源代码中知道如何解决这个难题,即需要返回什么才能满足所有推断的类型,从而实现它所需的特征。

答案很可能距离我所在的代码很远。这就像排查一个距离问题根源很远的异常,但更糟糕的是,因为它是由遥远的类型推断体操造成的。

程序员有一种倾向,就是编写一些「结」,从而扼杀了他们所有同事的快乐。这是一个累积的过程;他们慢慢地将「结」越来越多地堆积起来,将其全部保持在他们的短期记忆中,因此他们能够理解它,并且看不到「结」,如果他们看到了,他们就会有沉没成本谬误。在我的经验中,似乎存在一个代码复杂度守恒原理。极端的复杂度就是痛苦。

复杂度可以集中在一个区域,它通常是一种选择,一种权衡,但经常会偶然发生,就像上面一样。哎呀,我编织了一个「结」。你可以用更复杂的方式实现相同的逻辑,但不会带来痛苦。复杂度被分散,这样当你从宏观角度来看时,它集中在整个代码库中,但当你从微观角度来看时,每个部分都是可理解的,并且没有痛苦。

这就是我的理论,我认为这也是 Go 语言的创建者所持有的理论,以及他们为什么认为超简单的语言是 Google 这样大型快速发展的公司扩展软件的最佳方式。

这是一个例子。我不知道这对作为 Actix 框架用户的我意味着什么。我无法仅仅从代码中弄清楚它对我意味着什么,我需要做什么。我的潜意识感觉它正在试图处理一个悖论,它无法向我的意识大脑表达,甚至无法提出正确的问题。

use std::future::{ready, Ready};
use actix_web::{
dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform},
Error,
};
use futures_util::future::LocalBoxFuture;
// 中间件处理有两步。
// 1. 中间件初始化,中间件工厂使用
// 下一个服务链作为参数调用。
// 2. 中间件的 call 方法使用普通请求调用。
pubstructSayHi;
// 中间件工厂是 `Transform` 特征
// `S` - 下一个服务的类型
// `B` - 响应主体类型
impl<S, B> Transform<S, ServiceRequest> for SayHi
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static,
B: 'static,
{
typeResponse = ServiceResponse<B>;
typeError = Error;
typeInitError = ();
typeTransform = SayHiMiddleware<S>;
typeFuture = Ready<Result<Self::Transform, Self::InitError>>;
fnnew_transform(&self, service: S) -> Self::Future {
ready(Ok(SayHiMiddleware { service }))
}
}
pubstructSayHiMiddleware<S> {
service: S,
}
impl<S, B> Service<ServiceRequest> for SayHiMiddleware<S>
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static,
B: 'static,
{
typeResponse = ServiceResponse<B>;
typeError = Error;
typeFuture = LocalBoxFuture<'staticResult<Self::Response, Self::Error>>;
forward_ready!(service);
fncall(&self, req: ServiceRequest) -> Self::Future {
println!("Hi from start. You requested: {}", req.path());
let fut = self.service.call(req);
Box::pin(asyncmove {
let res = fut.await?;
println!("Hi from response");
Ok(res)
})
}
}








文章精选

「Rust