欢迎参与 8 月 1 日中午 11 点的线上分享,了解 GreptimeDB 联合处理指标和日志的最新方案! 👉🏻 点击加入

Skip to content
On this page
技术
2023-5-12

编程界的新星 — Rust 凭什么被业界青睐(内附学习资源)

本文将向大家介绍 Rust 的起源、优势,并为有意学习 Rust 语言的朋友们提供一些我们认为还不错的学习资源。

Rust 语言,作为一门充满活力的计算机编程语言,近年来逐渐成为业界的焦点。在 Stack Overflow 的年度开发者调查报告中,Rust 连续多年被评为“最受喜爱的编程语言”,越来越多的大公司如谷歌、微软、腾讯等都开始将其运用于各类项目中。

尽管 Rust 语言颇受欢迎,我们发现很多学生和初级程序员对它的了解仍显不足。作为一个使用 Rust 语言开发的开源数据库项目团队,我们希望能帮助更多人深入了解这门安全、高效的编程语言。因此,本文将向大家介绍 Rust 的起源、优势,并为有意学习 Rust 语言的朋友们提供一些我们认为还不错的学习资源

Rust 语言是什么

由来

Rust 最初由 Mozilla 的员工 Graydon Hoare 开发,Mozilla 于 2009 年开始赞助该项目,作为正在进行的名为 Servo 的实验性浏览器引擎开发的一部分,并于 2010 年首次亮相。

Rust 的开发团队在 Mozilla 的帮助下,不断改进语言的设计和实现,直到 2015 年 Rust 1.0 正式发布。Rust 的设计灵感来自于 C++、C#、Haskell 和 Erlang 等多种编程语言,具有其它语言所不具备的一些独特特性。作为一门现代系统编程语言,其旨在提供内存安全、高性能和可靠性

Rust 语法

Rust 的语法类似于 C++,它采用了一些现代编程语言的特性,如模式匹配、闭包和迭代器等,同时也继承了 C++ 的语法和底层系统编程能力。而为了解决 C++ 存在的内存安全问题和并发性问题,Rust 引入了一个严格的所有权模型和类型系统,能够在编译期间检测内存错误和数据竞争问题,从而确保程序的安全性和稳定性

Rust 的优势

高性能

Rust 的高性能是指其能够在保持内存安全和语言特性丰富的前提下,提供接近于 C++ 的高性能表现。Rust 的高性能主要得益于其对于内存分配、多线程处理和零成本抽象等方面的优化。

如果把 Rust、C++ 和 Golang 比作三个要参加跑步比赛的运动员:

C++ 无疑是一位经验丰富的选手,他有很高的速度和灵活性。然而,C++ 需要自己负责“调整呼吸和心率”等许多事项(内存管理),这使得他在比赛中容易出错,导致失速或摔倒(内存泄漏、悬垂指针等问题)。

Golang 运动员则是一位新锐选手,其优势在于有一个智能的教练(垃圾回收器)在比赛中为他调整呼吸和心率。虽然这样可以避免很多失误,但是需要不断与教练沟通,这会减慢他的速度。

而 Rust 运动员则是一位全能选手。他借鉴了 C++ 运动员的速度和灵活性,同时又学会了像 Golang 一样在比赛前就制定好详细的计划(所有权、借用和生命周期等机制)。这使得 Rust 运动员在比赛中既能保持高速度,又能避免失误。

(图 1:Discord 负载测试)
(图 1:Discord 负载测试)

Source: Discord 上图是 Discord 进行的负载测试,明显可以看到用 Golang 的情况下,每隔一段时间系统就会有一个负载峰值,这就是垃圾回收器在作怪,而改成 Rust 实现之后,整体就平滑很多。同时在 Rust 版本中,延迟、CPU 和内存都要好一些。

可靠性

想象一下 Rust 和其他编程语言(如 C++)是两家餐厅,它们各自的可靠性是如何体现在食物制作过程中的呢?

C++ 餐厅制作食物的过程相对自由。厨师可以随意调整食材、烹饪方法和加工方式。虽然这样的灵活性可能带来很多创新,但在这个过程中,食物容易受到污染,比如厨师在切生鱼片时使用了切生肉的刀具,可能导致食物中毒。这就像 C++ 在处理内存和资源时,它的灵活性可能导致诸如内存泄漏、空指针解引用和数据竞争等问题。

而 Rust 餐厅则更加严格和规范。在制作食物的过程中,Rust 餐厅会对食材的来源、厨师的操作、烹饪方法等进行严格的监管。比如,它会要求在切生鱼片之前,厨师必须先换一把干净的刀具。Rust 餐厅的严格监管确保了食物质量和安全性,减少了因为疏忽而导致的问题。

Rust 在编译阶段通过借用检查器(Borrow Checker)进行内存安全检查,确保在内存操作过程中遵循严格的规范。通过编译时的检查减少程序运行时的错误,从而提高了整体的代码质量和安全性。

内存安全

Rust 的内存安全优势在于,这些机制会在编译阶段就检查程序的内存管理是否正确,防止程序员在编写代码时犯下诸如内存泄漏、双重释放和悬垂指针等错误

这就像餐厅的管理者预先规划了菜肴的烹饪和清理流程,确保餐厅始终保持卫生和高效。因此,相较于其他编程语言,Rust 更能确保内存安全和程序的稳定性。

我们尝试写点代码看看,下面是一个错误的示例:

rust
fn main() {
    let r;

    {
        let x = 5;
        r = &x;
    }

    println!("r: {}", r);
}

在上述这段代码中,我们在外部作用域中定义了一个 r。在内部作用域中,定义了 x, 并把 x 的引用赋值给了 r,最后我们在外部作用域中访问了 r

我们尝试编译下上述代码,Rust 编译器会给出详细的错误信息。如下所示:

rust
error[E0597]: `x` does not live long enough
 --> src/main.rs:6:13
  |
5 |         let x = 5;
  |             - binding `x` declared here
6 |         r = &x;
  |             ^^ borrowed value does not live long enough
7 |     }
  |     - `x` dropped here while still borrowed
8 |
9 |     println!("r: {}", r);
  |                       - borrow later used here

For more information about this error, try `rustc --explain E0597`.
error: could not compile `safe_memory` (bin "safe_memory") due to previous error

那为什么是错误的?上面的报错信息就已经很详细了,我们复述一下。因为变量 x 并没有 “存在地足够久”。当 x 到达第七行时,就离开了作用域,会被 drop 掉,此时外部作用域的 r 就变成了悬垂引用,所以在第 9 行访问 r 时,就会报错。

如果去掉第九行的话,编译就会通过。因为编译器知道在 x 被 drop 之后 ,没有地方可以再访问到悬垂引用 r 了。

小结一下:Rust 的一个重要特性是内存安全,Rust 编译器在编译期间会检查程序中的内存安全问题,并尽可能让这些问题在编译期暴露出来,从而避免运行时出现内存错误。

并发安全

Rust 的并发安全是指其能够在多线程环境下保证线程安全,避免出现数据竞争和死锁等问题。Rust 的并发安全主要依靠其所有权、借用机制及生命周期,当一个值被多个线程引用时,Rust 会阻止这些线程同时访问这个值的可变引用,从而避免了数据竞争问题。

下面这个 demo 演示了如何使用 Rust 中的原子类型来实现一个线程安全的累加器。

rust
use std::sync::{
    atomic::{AtomicI32, Ordering},
    Arc,
};

fn main() {
    let counter = Arc::new(AtomicI32::new(0));
    let mut handles = vec![];

    for _ in 0..10 {
        let counter = counter.clone();
        let handle = std::thread::spawn(move || {
            for _ in 0..1000 {
                counter.fetch_add(1, Ordering::Relaxed);
            }
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.join().unwrap();
    }
    // the result should be 10*1000=10000 
    println!("Counter value: {}", counter.load(Ordering::Relaxed));
}

Rust 生态

Rust 语言虽然没有 JAVA、C++ 那么成熟,但近年来它周边的生态系统也在不断发展,已经拥有了很多库和框架。 一些 Rust 周边生态中的主要部分有:

包管理器Cargo 是 Rust 的官方包管理器,用于构建、测试、发布 Rust 应用程序和库。

Web 开发ActixRocket 是 Rust 中最受欢迎的 Web 框架,提供了构建高性能、安全的 Web 应用程序所需的功能。

异步编程Tokio 和 async-std 是 Rust 中用于异步编程的两个主要库,提供了异步 I/O,定时器和任务调度等功能。

操作系统开发:Rust 在操作系统开发方面也具有潜力,Redox 是一个用 Rust 编写的微内核操作系统,代表了 Rust 在系统编程领域的实力。

这只是 Rust 生态系统中的一部分。随着 Rust 社区的不断发展,可以期待更多的库和框架会出现。

Rust 学习渠道

除了在 Rust 的官方 GitHub 入门之外,还可以通过以下书籍进行学习,这些书籍涵盖了从基础到高级的 Rust 知识,适合不同水平的读者阅读。

  • 入门

    • The Rust Programming Language

    • Programming Rust

  • 中级

    • Rust for Rustaceans

    • Rust Atomics and Locks

    • The Rust Reference

  • 高级

    • The Rustonomicon

当然也可以看 Rust 专家的视频讲解,比如:

Jon Gjengset(youtube.com/c/jongjengset),这是一位讲解 Rust 语言的博主,同时他也是麻省理工学院的教授,他的视频内容会更加进阶,适合已经有一定 Rust 语言基础的受众。

入门之后,还可以通过一些练手项目更深入地学习和使用 Rust,比如:

  • Tokio,一个异步的运行时用于编写高性能的网络应用程序

  • Serde,一个用于序列化和反序列化 Rust 数据结构的库

  • Diesel,一个 Rust 的 ORM(对象关系映射)库,用于将 Rust 数据结构映射到关系型数据库中

这些项目都可以帮你在 Rust 的使用方面更加精进。

当然,你也可以选择我们的 GreptimeDB 项目进行练习,GreptimeDB 是用 Rust 语言编写的开源时序数据库项目。我们提供了一系列适合新手的 good first issues,欢迎前往 GitHub 查看我们的项目。

总结

在 1.0 发布前,Rust 断舍离了内部的 green threads 实现,彻底坚定了这门语言作为一个无运行时、低抽象层级的设定。即便是之后标准库引入了异步 Future API,这一特色仍然保持。

低抽象级别带来的更细更强的控制力,使得 Rust 开发者可以更精准地控制资源(线程、内存),非常适合诸如服务器、数据库等场景的开发,直接对标 C++ 的传统领域。但控制力的背面是高风险,C++ 因其风险性广受诟病,也促使 Mozilla 选择尝试新语言。与之前 20 年中诸如 Java 、Go 等通过牺牲控制力降低风险的解决方案不同,Rust 选择用编写和编译时的强限制,比如让人又爱又恨的 borrow checker 和 lifetime ,保全了运行时的高性能和安全性

当然,代价是相对陡峭的学习曲线和较长的编译时间。在这方面,Rust 在早期就成立了工具团队,持续在用更好的开发工具来降低其负面影响。

关于 Greptime

Greptime 格睿科技专注于为可观测、物联网及车联网等领域提供实时、高效的数据存储和分析服务,帮助客户挖掘数据的深层价值。目前基于云原生的时序数据库 GreptimeDB 已经衍生出多款适合不同用户的解决方案,更多信息或 demo 展示请联系下方小助手(微信号:greptime)。

欢迎对开源感兴趣的朋友们参与贡献和讨论,从带有 good first issue 标签的 issue 开始你的开源之旅吧~期待在开源社群里遇见你!添加小助手微信即可加入“技术交流群”与志同道合的朋友们面对面交流哦~

Star us on GitHub Now: https://github.com/GreptimeTeam/greptimedb

官网:https://greptime.cn/

文档:https://docs.greptime.cn/

Twitter: https://twitter.com/Greptime

Slack: https://greptime.com/slack

LinkedIn: https://www.linkedin.com/company/greptime/

加入我们的社区

获取 Greptime 最新更新,并与其他用户讨论。