前言

Rust 现已悄然成为了最受欢迎的编程言语之一。作为一门新式底层体系言语,Rust 拥有着内存安全性机制、挨近于 C/C++ 言语的功能优势、出色的开发者社区和体会出色的文档、东西链和IDE 等诸多特点。本文将介绍笔者运用 Rust 重写项目并逐渐落地生产环境的进程,以及在重写进程挑选 Rust 的原因、遇到的问题以及运用 Rust 重写带来的效果。

咱们目前正在运用 Rust 开发的项目叫做 KCL,目前全部完成代码现已在 Github 上开源。KCL 是一个根据约束的记录及函数范畴编程言语,致力于经过老练的编程言语技能和实践来改进特范畴如云原生 kubernetes 范畴的大量繁杂装备编写和安全策略校验等,致力于构建环绕装备的更好的模块化、扩展性和稳定性,更简略的逻辑编写,以及更快的自动化集成和良好的生态延展性。更详细的 KCL 运用场景请访问 KCL 网站,本文中不再过多赘述。

KCL 之前是运用 Python 编写的,出于用户运用体会、功能和稳定性的考虑,决议用 Rust 言语进行重写,并取得了以下优点:

  • 更少的 Bug,源于 Rust 强壮的编译查看和过错处理方式
  • 言语端到端编译执行功能提高了 66%
  • 言语前端解析器功能提高了 20 倍
  • 言语中端语义分析器功能提高了 40 倍
  • 言语编译器编译进程均匀内存运用量变为本来 Python 版别的一半

咱们遇到了什么问题

就像社区中同类型项目 deno, swc, turbopack, rustc 等编译器、构建体系或者运转时在技能上运用 Rust 做的事情相似,咱们运用 Rust 完好构建了编译器的前中端和运转时,取得了必定的阶段性效果,可是咱们大约在一年前并不是这个样子的。

一年前,咱们运用 Python 言语构建了整个 KCL 言语编译器的完成,虽然在一开端的时候运转良好,Python 简略易上手,生态丰富,团队的研制功率也很高,可是跟着代码库的扩张和工程师人数的添加,代码保护起来愈加困难,尽管咱们在项目中强制编写 Python 类型注解,选用更严厉的 lint 东西,代码测试行覆盖率也达到了 90% 以上,可是仍然会呈现许多比方 Python None 空对象,属性未找到等运转时才会呈现过错,而且重构 Python 代码时也需求小心谨慎,反应到 KCL 言语上便是一个接一个的 bug, 严重影响用户运用体会。

此外,当 KCL 运用对象是广大开发者用户时,编程言语或者说编译器内部完成呈现任何过错都是不行容忍的,这些也给咱们的用户运用体会带来了一系列问题,运用 Python 编写的程序发动速度较慢,功能无法满意自动化体系在线编译和执行的功率诉求,由于在咱们的场景中,用户修正 KCL 代码后需求能很快的展示编译结果,显然运用 Python 编写的编译器并不能很好地满意运用需求。

为什么挑选 Rust

笔者所在团队根据如下原因挑选了 Rust

  • 运用 Go, Python, Rust 三种言语完成了简略的编程言语栈式虚拟机并作了功能对比,Go 和 Rust 在这个场景下功能挨近,Python 有较大功能距离,归纳考虑下选用了 Rust,详细三种言语完成的栈式虚拟机代码细节在 github.com/Peefy/Stack…,感兴趣的同学能够前往阅读
  • 越来越多的编程言语的编译器或运转时特别是前端基础设施项目选用 Rust 编写或重构,此外基础设施层,数据库、搜索引擎、网络设施、云原生、UI 层和嵌入式等范畴都有 Rust 的呈现,至少在编程言语范畴完成方面经过了可行性和稳定性验证
  • 考虑到后续的项目发展会涉及区块链和智能合约方向,而社区中大量的区块链和智能合约项目选用 Rust 编写
  • 经过 Rust 取得更好的功能和稳定性,让体系更容易保护、愈加强健的一起,能够经过 FFI 露出 C API 供多言语运用和扩展,方便生态扩展与集成
  • Rust 对 WASM 的支撑比较友好,社区中大量 WASM 生态是由 Rust 构建,KCL 言语和编译器能够借助 Rust 编译到 WASM 并在阅读器中运转

根据以上原因归纳考虑挑选了 Rust 而不是 Go,整个重写进程下来发现 Rust 归纳本质的确过硬(第一队伍的功能,满足的抽象程度),虽然在一些言语特性特别是生命周期等上手本钱有一些,生态上还不够丰富,总归编程言语能够做的事情,Rust 均能够做,详细或许还是要根据详细的场景和问题来做挑选。一起假如想要运用好 Rust, 还需求深化了解内存、仓库、引证、变量效果域等这些其它高档言语往往不会深化接触的内容。

运用 Rust 进程中遇到了哪些困难

虽然决议了运用 Rust 重写整个 KCL 项目,其实团队成员大部分成员是没有运用 Rust 编写必定代码体量项目的经历,包含笔者个人自己也只是学习过 《The Rust Programming Language》 中的部分内容,依稀记得学习到 RcRefCell 等智能指针内容就放弃了,那时没想到 Rust 中还能有与 C++ 中相似的东西。

运用 Rust 前预估的风险主要是 Rust 言语接触和学习的本钱,这个的确在各种 Rust 的文章博客中均有提到,由于 KCL 项目整体架构并未产生太大变化,只是部分模块规划和代码编写针对 Rust 作了优化,因而整个重写是在边学边实践中进行。的确在刚开端运用 Rust 编写整个项目的时候花费在常识查询、编译排错的时刻还是许多的,不过跟着项目的进行渐入佳境,笔者个人经历运用 Rust 遇到的困难主要是心智转换和开发功率两方面:

心智转换

首先 Rust 的语法语义很好地吸收和交融了函数式编程中类型体系相关的概念,比方抽象代数类型 ADT 等,而且 Rust 中并无“继承”等相关概念,假如不能很好地了解甚至连其他言语中稀松平常的结构界说在 Rust 中或许都需求花费不少时刻,比方如下的 Python 代码或许在 Rust 中的界说是这个样子的。

  • Python
from dataclasses import dataclass
class KCLObject:
    pass
@dataclass
class KCLIntObject(KCLObject):
    value: int
@dataclass
class KCLFloatObject(KCLObject):
    value: float
  • Rust
enum KCLObject {
    Int(u64),
    Float(f64),
}

当然更多的时刻是在与 Rust 编译器自身的报错作斗争,Rust 编译器会常常使开发人员”碰壁”,比方借用查看报错等,特别是对于编译器来讲,它处理的核心结构是抽象语法树 AST,这是一个递归和嵌套的树结构,在 Rust 中有时很难统筹变量可变性与借用查看的联系,就如 KCL 编译器效果域 Scope 的结构界说结构那样,对于存在循环引证的场景,用于需求显现意识到数据的相互依赖联系,而大量运用 Rc, RefCellWeak 等 Rust 中常用的智能指针结构。

/// A Scope maintains a set of objects and links to its containing
/// (parent) and contained (children) scopes. Objects may be inserted
/// and looked up by name. The zero value for Scope is a ready-to-use
/// empty scope.
#[derive(Clone, Debug)]
pub struct Scope {
    /// The parent scope.
    pub parent: Option<Weak<RefCell<Scope>>>,
    /// The child scope list.
    pub children: Vec<Rc<RefCell<Scope>>>,
    /// The scope object mapping with its name.
    pub elems: IndexMap<String, Rc<RefCell<ScopeObject>>>,
    /// The scope start position.
    pub start: Position,
    /// The scope end position.
    pub end: Position,
    /// The scope kind.
    pub kind: ScopeKind,
}

开发功率

Rust 的开发功率能够用先抑后扬来描述。在刚开端上手写项目时,假如团队成员没有接触过函数式编程相关概念以及相关的编程习惯,开发速度将显着慢于 Python、Go 和 Java 等言语,不过一旦开端了解 Rust 标准库常用的方法、最佳实践以及常见 Rust 编译器报错修正,开发功率将大幅提高,而且原生就能写出高质量、安全、高效的代码。

比方笔者个人当初遇到一个如下代码所示的与生命周期过错前前后后排查了很久的时刻才发现本来是忘掉标示生命参数导致生命周期不匹配。此外 Rust 的生命周期与类型体系、效果域、所有权、借用查看等概念耦合在一起,导致了较高的了解本钱和复杂度,且报错信息往往不像类型过错那么显着,生命周期不匹配过错报错信息有时也略显板滞,或许会导致较高的排错本钱,当然了解相关概念写多了之后功率会进步不少。

struct Data<'a> {
    b: &'a u8,
}
// func1 和 func2 一个省掉了生命周期参数,一个没有省掉
// 对于 func2 的生命周期会由编译器缺省推导为 '_,或许导致生命周期不匹配过错
impl<'a> Data<'a> {
    fn func1(&self) -> Data<'a> {Data { b: &0 }}
    fn func2(&self) -> Data {Data { b: &0 }}
}

运用 Rust 重写收益比

经过团队几个人花费几个月时刻运用 Rust 完全重写并稳定落地生产环境几个月后,回忆整个进程感觉这件事情的收成非常大,从技能视点层面来看,重写的进程不只是训练了快速学习一门新的编程言语、编程常识并将其付诸实践,而且整个重写进程让咱们又反思了 KCL 编译器中规划不合理的部分并进行修正,对一个编程言语而言,这是一个长周期的项目,咱们收成的是编译器体系愈加稳定、安全,且代码明晰,bug 更少、功能更好的技能产品服务于用户,虽然没有全部模块得到高达 40 倍的功能,由于部分模块如 KCL 运转时的功能瓶颈在于内存深拷贝操作,但笔者个人认为仍然是值得的。且当 Rust 运用时刻抵达必定时长后,心智和开发功率不再是约束因素,就像学车那样,拿到驾照后更多是上路实践和总结。

结语

笔者个人觉得运用 Rust 重写项目后最重要的是不是我学会了一门新的编程言语,也不是 Rust 很流行很火因而咱们在项目中选用一下,或者运用 Rust 编写了多少炫技的代码,是真真正正地使得言语和编译器自身愈加稳定,能够在生产环境平稳落地并长期运用,发动速度和自动化功率不再受困扰,功能优于社区其他同类型范畴编程言语,使咱们言语和东西的用户感受到体会提高,这些都得益于 Rust 的无 GC、高功能、更好的过错处理内存办理、零抽象等特性。总归作为用户,他们才是最大的受益者。

最终,假如大家喜欢 KCL 言语这个项目,或想运用体会 KCL 用于自己的场景,或想运用 Rust 言语参加一个开源项目,欢迎大家访问 github.com/KusionStack… 参加咱们的社区一起参加评论和共建 。

参阅

  • github.com/KusionStack…
  • github.com/Peefy/Stack…
  • doc.rust-lang.org/book/
  • github.com/sunface/rus…
  • www.influxdata.com/blog/rust-c…