首页
Preview

从 Rust 到 TypeScript 的转换之路

由于程序员往往会专门化,因此语言比较也往往形成他们的小圈子。因此你可能会在 C++ 和 Rust、Ruby 和 PHP 之间转换,但在圈子之间移动的情况却很少。几乎就像移动到完全具有全新文化的地球上的另一边的新国家,而不是几英里之外的新城市一样。

因此,我想分享一下我的笔记,记录我作为系统级 Rust 程序员到一个不太可能的竞争者 Typescript 的旅程!

你为什么要这样做?

任何人心中的第一个问题应该是为什么我要这样做,特别是因为我之前在低级存储系统和数据库上做了这么多工作。

为什么????

众所周知,我最近创立了 ChiselStrike。ChiselStrike 是对 Firebase 在 2022 年设计的现代化版本的一个探索(使应用程序开发人员可以轻松地将他们的后端作为副产品进行托管)。在高层次上,我们抽象出整个后端,将其作为可消费的服务提供。独特之处在于,我们抽象出了后端开发的大部分常见元素(如数据库和身份验证)。因此,开发人员只需用干净、纯净的 Typescript 表达他们从后端所需的内容,我们就会处理剩下的部分。为什么是 Typescript?因为(如果与 Javascript 计算在一起)它在应用程序开发人员中占据主导地位。

因此,我们自然希望将所有的数据库和低级代码都写成 Rust,但与此同时,所有的 API 和用户可见交互都将在 Typescript 中发生。一旦做出了这个选择,我的命运就已经注定了:我必须去学习和掌握 Typescript。

我喜欢 Typescript 的什么

与 Rust 相比,我最喜欢 Typescript 的第一件事情就是它有多么富于表现力。在 Rust 中,处理字符串之类的事情是一件绝对的痛苦。它可能比 C 和 C++ 好,但这并没有多大意义。

函数可以使用非常简洁的语法来接受多种类型:a: T1 | T2。在 Rust 中,你必须使用枚举。Rust 的方式确实有一个优点,强制你检查所有可能的变体,但是由于 Typescript 是一种更高级的语言,仅表达类型可选性的便利性是方便的,而且我认为这种语法非常愉悦。

对象本来就是 JSON,这是...令人惊讶的方便?一开始感觉很奇怪,但不到一天的时间,我就开始怀念这种方便了,回到 Rust 并被迫反复序列化东西。

Typescript 也非常友好于异步,而且一切都是单线程的。这是一个有趣的问题:我猜大多数人会认为这是一个缺点,但是在两个每个核心都有两个线程的框架中工作过(分别是C++Rust),我喜欢 Typescript 很好地符合这种模型。

例如,在 ChiselStrike 中,HTTP 服务器(使用 Rust 编写,使用 hyper)是一个线程-每-核心服务器。我们通过在接受连接时传递 SO_REUSEPORT 标志,然后基本上有多少个执行者就有多少个线程来实现。在每个线程中,都有一个 v8 实例。由于 http 请求是独立的,这个方法完全可以正常工作。Boom!线程-每-核心!

Typescript 非常适合异步代码,并且很容易产生像线程-每-核心这样的高性能架构。

Typescript 的另一个巨大优势是它释放了一些非常难以通过其他方式获得的东西:在浏览器和服务器中透明地工作的能力。生态系统也非常令人印象深刻。有超过 1.3M npm 可用包,比 crates.io 多了数倍。

我讨厌 Typescript 的什么

我之前对 Javascript/Typescript 生态系统的印象是有点混乱。我听说过缺少本地整数类型的经典问题,这使得类型强制转换有令人惊讶的结果。总的来说,这一切都是真的。特别是,即使我在理论上知道这一点,我仍然最终得到了有缺陷的代码。如果你愿意,可以归咎于经验不足,但我仍然保留讨厌它的权利!我想找出特定的http响应是否成功,所以是“200s中的任何一个”。我该如何写?

const success = req.ret / 100 == 2

如果返回代码是200,则可以正常工作,但如果返回代码是201或更高,则无法正常工作。我的Typescript专家朋友很耐心地告诉我,在Typescript中通常使用if语句测试两个边界,例如if ret ≥ 200 && ret < 300。如果你是长期从Rust和C++转向Typescript的开发人员,现在认为我很愚蠢,我向你发誓,这在任何从Rust和C++转向Typescript的人的头脑中都是完全合理的。原因有两个:

  • 当你习惯处理低级代码时,你会有点害怕处理器的分支预测,通常尽可能避免分支(尽管在这种情况下它可能会预测良好)。虽然除法通常不是最快的处理器操作,但至少它是一致的,在许多情况下,你可以使用移位和位操作来重写它们(在这种情况下不能),因此这是我们自然而然的方法。
  • 100是一个整数,201也是。在任何这些其他语言中,201/100为2,因为这是整数除法的工作方式。但在Typescript中,201/100 = 2.01,上面的代码必须重写为:
const success = (req.ret / 100 | 0) == 2

我花了10年时间编写内核代码,作为一个经验法则,你甚至不能使用浮点单元。在我与其他任何语言的交互中,整数都是默认值。所以这真的很烦人。当然,你会习惯它,但这是我的笔记,我的旅程,这对我来说很烦人!=)

但如果我必须选择我最不喜欢的东西...那就是模块系统。说真的,我甚至不知道从哪里开始。也许对于处理这个生态系统多年的人来说,这是有意义的,但是CommonJS、ESM和所有不同的标准的整个问题都超出了理解范围。这让我想起了Python2与Python3的争论,这在我谦虚的看法中可能是我们整个行业最大的灾难。

虽然我们喜欢Deno,但它肯定没有帮助我对Typescript的总体印象,因为Deno希望导入是包括文件扩展名的实际URL,而tsc将会在扩展名实际存在时发出警报。

现在,一旦我们确定了这是不好的,并且将其作为生活的事实接受,我仍然期望现代大多数东西中这种问题主要出现在偏僻的模块中。

但令我惊讶的是,即使是fetch也会出现问题。首先,我很惊讶甚至需要一个模块来完成这个操作。在这种环境中,它感觉像是我想做的最基本的事情。node-fetch的npm页面以以下方式打开:

“Instead of implementing <em>XMLHttpRequest</em> in Node.js to run browser-specific Fetch polyfill, why not go from native <em>http</em> to <em>fetch</em> API directly?”

Polyfills是一个你从这个生态系统之外很少听说的词语……用来描述在这个生态系统之外经常发生的事情!

首先,让我告诉你,作为Typescript世界的外国人,我的大脑将那个短语翻译为“F *** You”。有了polyfill的概念后,这就有了一点意义,但遗憾的是:node-fetch,一个每周下载量为1300万的包,是一个仅限于ESM的模块。就像Python2和Python3一样,一个系统与另一个系统的选择通常已经存在,因此你最终会被困住。当然,node-fetch本身的npm页面声称有一个可以使用CommonJS的替代版本的软件包,但整个过程非常令人沮丧。

我第一次尝试使用node-fetch的实际镜头。

最后,我实际上发现了一个有趣的设计决策,那就是Typescript实际上只是在Javascript之上的一堆注释。我喜欢向后兼容性和互操作性,所以当我了解到这一点时,我感到非常高兴。

然而,将这些东西称为“类型”有点牵强。考虑以下两种类型:

type First = { a: string }
type Second = { a: string }

接受第一个的函数也会很乐意接受第二个。在Rust中,我习惯于在POD类型上定义包装器,这在你有一个函数同时接受许多参数的类型,并且想要防御性地确保你永远不会更改它们的顺序时非常方便。例如,你可以编写:

fn my_func(first: FirstBool, second: SecondBool)

这里有一些过程宏,一些类型强制转换,你甚至不需要任何重要的样板文件。

在Typescript(我们应该称其为Shapescript吗?)中,这是不可能的,这真的很令人沮丧。

在这个方向上的一口清新空气是DeepKit。虽然编译时类型仍然与以前相同,但DeepKit保留了运行时的类型信息。当我们考虑ChiselStrike的翻译层应该是什么样子时,我们想用以下方式表达独特性:

foo: Unique<string>,

这在Typescript中完全不可能,因为Unique在Typescript中没有任何意义。我们最终使用了装饰器。DeepKit允许我们编写这样的内容:

foo: string & Unique

虽然我更喜欢&lt;&gt;语法,但现在这真的只是个人惯性。我对DeepKit感到非常兴奋,密切关注他们将能够开启什么样的新世界。

这真的是一个独特的选择吗?

归根结底,对我来说最大的惊喜是,这并不是一个过渡。就像一个保留旧家并经常拜访的移民一样,当我环顾四周并意识到我的生活现在用Typescript和Rust表达时,我感到非常愉快。我们选择了Deno作为我们的运行时环境。它不仅是用Rust编写的,让我们能够轻松修复错误和改进它,而且还能轻松地在两个世界之间移动东西。

虽然序列化有一定的成本,但是通过Deno从TypeScript调用Rust函数相对容易,并且一直表现良好。

只需添加#[op] proc-macro。现在,该函数可以在TypeScript中使用deno进行调用。

译自:https://medium.com/chiselstrike/notes-from-my-journey-from-rust-to-typescript-9bd9d28141e3

版权声明:本文内容由TeHub注册用户自发贡献,版权归原作者所有,TeHub社区不拥有其著作权,亦不承担相应法律责任。 如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

点赞(0)
收藏(0)
一个人玩
先找到想要的,然后出发

评论(0)

添加评论