初探 Rust

Rust 编程语言Rust 是一种新兴的编程语言,专注于性能、并行化和内存安全。通过从头构建语言并结合现代编程语言设计元素,Rust 的创建者避免了许多传统语言必须面对的“包袱”(向后兼容性要求)。相反,Rust 能够将高级语言的表达语法和灵活性与低级语言前所未有的控制和性能融为一体。

选择编程语言通常涉及权衡。虽然大多数现代高级语言提供了用于安全并发和内存安全的工具,但它们会带来额外的开销(例如,使用 GC),并且往往缺乏性能和细粒度的控制。

为了应对这些限制,人们不得不求助于低级语言。没有大多数高级语言的安全网,这可能很脆弱且容易出错。人们突然需要处理手动内存管理、资源分配、悬空指针等。创建能够利用当今设备中不断增长的核心数量的软件很困难——确保代码正确运行更难。

那么 Rust 如何在一门语言中结合两者的优点呢?这正是我们在本文中要向您展示的内容。 Rust 1.0.0 稳定版 刚刚发布。该语言已经拥有一个 活跃的社区,一个不断增长的 箱子生态系统(库)在其包管理器中,以及利用其功能的开发人员在各种项目中使用它。即使您从未接触过低级语言,现在也是您深入了解 Rust 的绝佳时机!

全球各地社区在 'Rust 发布派对' 上庆祝 Rust 首个稳定版本的发布Rust T 恤庆祝 Rust 1.0 在全球范围内的发布.

Rust 的当前用例

因此,对于系统黑客来说,Rust 似乎是一个不错的选择,但对于不熟悉低级语言的人来说呢?也许您上次听到“C”和“堆栈/堆分配”这两个词是在 10 年前的 CompSci 101 课程上(或者从未听到过)。Rust 提供了通常仅在低级系统语言中才能见到的性能——但大多数时候它确实感觉起来像高级语言!以下是一些您现在可以在实际应用中利用 Rust 的示例。

我想破解硬件/编写物联网应用程序

随着 物联网 时代的到来以及制造者运动的扩展,硬件项目的真正民主化成为可能。无论是树莓派、Arduino,还是 BeagleBone 或 Tessel 等新兴巨头,您都可以从大量语言中选择来编写您的硬件项目,包括 Python 或 JavaScript。

然而,在某些情况下,这些语言提供的性能根本不足。在其他情况下,您所针对的微控制器硬件不适合这些语言所需的运行时:速度慢的芯片,内存储备小,超低功耗的应用仍然需要一种接近底层的语言。传统上,这种语言一直是 C——但正如您可能已经猜到的那样,Rust 是新秀。

Rust 支持各种异类平台。虽然其中一些仍然是实验性的,但支持包括 通用 ARM 硬件、德州仪器 TIVA 开发板,甚至 树莓派。一些最新的物联网板,比如 Tessel 2,甚至提供了官方的开箱即用的 Rust 支持!

我正在运行扩展到多个内核的高性能计算应用程序

研究表明,Rust 非常适合 HPC(高性能计算)。您甚至不必用 Rust 重写整个应用程序:它灵活的 外部函数接口 (FFI) 提供了高效的 C 绑定,让您能够公开和调用 Rust 代码,而不会有任何明显的开销。这允许您逐个模块地重写您的应用程序,从而缓慢过渡到更好的开发体验,最终实现与旧代码相当或更好的性能。您还可以获得更易维护的代码库,错误更少,在大量内核上更具可扩展性。

我只是需要一些速度快的!

Rust 非常适合重写应用程序中对性能敏感的部分。它可以通过 FFI 与其他语言很好地交互,并且运行时很小,在大多数情况下,即使在资源有限的情况下,也能与 C 和 C++ 竞争。

尽管该语言仍在开发中,但已经有一些对业务至关重要的、实际生产中的应用程序已经使用 Rust 很长时间了:Yehuda Katz 的初创公司 Skylight 使用高性能 嵌入到 ruby gem 中的 Rust 代码 来进行数据处理。1.0.0 稳定版的发布也是一个重要的里程碑,从现在开始,不应该出现任何重大更改。这使得 Rust 即使对于最苛刻和最强大的应用程序来说也是安全的!

观看 Yehuda Katz 和 Tom Dale 讨论 Rust 编程的基础知识 以及他们在应用程序中如何使用 Rust 和 Ruby。

Rust 入门

有很多教程涵盖了 Rust 的一般内容,以及语言的特定方面。例如,Rust 博客 有关于开发 Rust 应用程序各个方面的精彩文章。还有一些优秀的介绍性演讲,例如 Aaron Turon 在斯坦福大学的演讲。他很好地解释了 Rust 背后的主要概念和动机,这场演讲是完美的开胃菜,可以帮助您开始您的旅程。

https://youtu.be/O5vzLKg7y-k

演讲和教程不能替代编写代码,对吧?Rust 在这方面也为您提供了支持!Rust 手册 的设计初衷就是帮助您入门——从安装到第一个Hello World,以及提供所有核心语言特性的深入参考。

另一个值得一提的资源是 Rust 实例,它将指导您完成 Rust 的关键功能,从基础知识到特质、宏和 FFI 的神奇力量。Rust 实例提供了宝贵的实时可编辑的浏览器内示例,涵盖所有文章。您甚至不必下载和编译 Rust,因为您可以在舒适的客厅沙发上尝试(和编辑)所有这些示例,就像它们一样。还有一个 Rust Playpen 用于完全相同的目的。

无论如何,下载和安装 Rust 并不是什么大问题。前往 Rust 主页 并下载相应的二进制文件/安装程序。下载内容包含开始使用所需的所有工具(如rustc,编译器,或cargo包管理器),并且为所有平台(Windows、Linux 和 OSX)预先构建。

那么,是什么让 Rust 如此独特,如此与众不同呢?

Rust 的方式

可以说,是 Rust 独一无二的 所有权模型 使它能够 真正闪耀——消除与线程和内存管理相关的整类错误,同时简化开发和调试。它在将语言运行时保持在最小限度,并将编译器输出保持精简和高效方面也至关重要。

所有权模型

Rust 所有权模型的基本原理是,每个资源都只能属于一个“所有者”。“资源”可以是任何东西——从一段内存到一个打开的网络套接字,再到像 互斥锁 这样的抽象事物。该资源的所有者负责使用、可能借出资源(给其他用户)以及最终清理资源。

所有这些都是自动发生的,借助作用域和绑定:绑定要么拥有,要么借用值,并持续到其作用域结束。随着绑定的作用域结束,它们要么归还借用的资源,要么在它们是所有者的情况下,处置资源。

更棒的是,所有权模型正常运行所需的检查在编译期间由 Rust 编译器和“借用检查器”执行和强制执行,这反过来会导致各种好处。

零成本抽象

由于程序的正确操作是在编译时断言的,因此编译的代码通常可以被认为是在大多数常见的内存和并发错误(例如 释放后使用错误数据竞争)方面是安全的。这是因为如果程序员尝试执行违反上述原则的操作,Rust 会在编译时报错,这反过来有助于 将语言运行时保持在最小限度
通过将这些检查推迟到编译时,无需在运行时执行它们(代码已经是“安全”的),也没有运行时开销。在内存分配的情况下,也不需要运行时垃圾收集。这种——高级语言结构几乎没有或根本没有运行时开销——是“零成本抽象”的基本思想。

尽管对所有权模型的全面解释超出了本文的范围,但 Rust 手册(上面链接)和各种演讲和文章在解释其原理方面非常出色。所有权原则和借用检查器对于理解 Rust 至关重要,并且是该语言其他强大方面的关键,例如它对并发和并行的处理。

两只鸟,一种语言结构

共享可变状态是万恶之源。大多数语言试图通过解决可变部分来解决这个问题,但 Rust 通过解决共享部分来解决这个问题。
Rust 手册

除了内存安全之外,并行和并发是 Rust 哲学中的第二个最重要的重点。这可能看起来很奇怪,但所有权系统(加上一些方便的特征)也为线程、同步和并发数据访问提供了强大的工具。

当您尝试一些内置的并发原语并开始深入研究该语言时,您可能会惊讶地发现这些原语是由标准库提供的,而不是语言核心本身的一部分。因此,提出新的并发编程方法取决于库作者——而不是硬编码到 Rust 中,受语言创建者的未来计划限制。

性能还是表现力?两者兼得!

由于其静态类型系统、精心选择的功能以及没有垃圾收集器,Rust 编译成性能良好且可预测的代码,其性能与使用传统低级语言(如 C/C++)编写的代码相当。

通过将各种检查从运行时移出并消除垃圾收集,生成的代码模仿了其他低级语言的性能特征,而语言本身仍然更加富有表现力。看似高级的结构(字符串、集合、高阶函数等)大多避免了与它们通常相关的运行时性能损失。由于 Rust 编译器的输出是 LLVM 中间表示,因此最终的机器代码充分利用了 LLVM 编译器的跨平台优势以及所有额外的(当前和未来的)优化自动进行。

我们可以继续谈论几个小时,关于如何使用 Rust 您永远不必 担心关闭套接字或释放内存,关于如何使用特征和宏为您提供难以置信的灵活性和 重载运算符,甚至使用 Rust 的函数式方面,处理迭代器、闭包和 高阶函数。但我们不想在一开始就让您不知所措!迟早您会遇到这些——自己发现兔子洞有多深会更有趣。

所以现在,让我们终于来看看一些代码。

向 Rust 说 hello

不多说——您的第一段 Rust 代码

fn main() {
    println!("Hello, Rust!");
}

在 Online Rust Playpen 中打开此代码片段 >>

等等,就这些?这可能看起来有点平淡无奇,但用 Rust 写 Hello World 只有这么多种方法。请耐心等待,我们会分享一个稍微复杂一点的例子——以及另一个永恒的经典。

这个 Fizzbuzz 是怎么回事

我们将使用 FizzBuzz 程序回归传统!Fizzbuzz 是一种算法,我们用它不断地向上计数,将某些数字替换为fizz(对于能被 3 整除的数字)、buzz(对于能被 5 整除的数字)或fizzbuzz(对于同时能被 3 5 整除的数字)。

为了证明我们的观点(并帮助您熟悉 Rust 的语义),我们还包括了相同算法的 C 和 Python 版本

命令式 fizzbuzz – C 版本

#include 

int main(void)
{
    int num;
    for(num=1; num<101; ++num)
    {
        if( num%3 == 0 && num%5 == 0 ) {
            printf("fizzbuzz\n");
        } else if( num%3 == 0) {
            printf("fizz\n");
        } else if( num%5 == 0) {
            printf("buzz\n");
        } else {
            printf("%d\n",num);
        }
    }

    return 0;
}

命令式 fizzbuzz – Python 版本

for num in xrange(1,101):
    if num%3 == 0 and num%5 == 0:
        print "fizzbuzz"
    elif num%3 == 0:
        print "fizz"
    elif num%5 == 0:
        print "buzz"
    else:
        print num

命令式 fizzbuzz – Rust 版本

fn main() {
    for num in 1..101 { // Range notation!
        match (num%3, num%5) { // Pattern Matching FTW!
            (0, 0) => println!("fizzbuzz"),
            (0, _) => println!("fizz"),
            (_, 0) => println!("buzz"),
                 _ => println!("{}", num) 
        }
    }
}

在 Online Rust Playpen 中打开此代码片段 >>

即使在这样的小片段中,Rust 的一些强大之处也开始显现。最明显的区别可能是我们使用的是 模式匹配,而不是传统的 if 语句。我们可以使用它们,但是 matchRustacean 武库中非常有用的补充。

需要注意的另一件事是 for 循环声明中的范围表示法(类似于其 Python 对应物)。有趣的是,在这种情况下,我们不能使用传统的 C 风格的 for 循环来代替——因为 它在 Rust 中是故意不支持的。传统的 for 循环被认为容易出错,并被更安全、更灵活的 可迭代概念 取代。

以下是对 fizzbuzz 示例中模式匹配阶段发生的事情的更详细的说明

...
 
// For pattern matching, we build a tuple, containing
// the remainders for integer division of num by 3 and 5
match (num%3, num%5) {
    // When "num" is divisible by 3 AND 5 both
    // (both remainders are 0)
    // -> print the string "fizzbuzz"
    (0, 0) => println!("fizzbuzz"),

    // When "num" is divisible by 3 (the remainder is 0)
    // Is "num" divisible by 5? -> we don't care
    // -> print the string "fizz"
    (0, _) => println!("fizz"),

    // When "num" is divisible by 5 (the remainder is 0)
    // Is "num" divisible by 3? -> we don't care
    // -> print the string "buzz"
    (_, 0) => println!("buzz"),

    // In any other cases, just print num's value
    // Note, that matching must be exhaustive (that is,
    // cover all possible outcomes) - this is enforced
    // by the compiler!
         _ => pintln!("{}", num)
}
...

这里不是深入了解模式匹配或解构如何工作,或者元组是什么的地方。您将在 Rust 手册Rust 博客Rust By Example 中找到关于这些主题的优秀文章,但我们认为这是一个很好的方法,可以展示使 Rust 强大而有效的特性和细微差别。

来点函数式 Fizzbuzz 怎么样

为了扩展上一个例子并展示 Rust 的真正灵活度,在第二个例子中,我们将把 fizzbuzz 提高一个档次!Rust 称自己为多范式。我们可以通过用函数式风格重写 fizzbuzz 示例来对这一点进行测试。为了帮助将代码置于正确的视角,我们还将展示 JavaScript(ECMAScript 6 风格)中的实现

函数式 fizzbuzz – JavaScript (ES6) 版本

Array.from(Array(100).keys()).slice(1)
    .map((num) => {
        if (num%3 === 0 && num%5 === 0) {
            return "fizzbuzz";
        } else if (num%3 === 0) {
            return "fizz";
        } else if (num%5 === 0) {
            return "buzz";
        } else {
            return num;
        }
    })
    .map(output => console.log(output));

函数式 fizzbuzz – Rust 版本

fn main() {
    (1..101)
        .map(|num| {
            match (num%3, num%5) { // Pattern Matching FTW!
                (0, 0) => "fizzbuzz".to_string(),
                (0, _) => "fizz".to_string(),
                (_, 0) => "buzz".to_string(),
                     _ => format!("{}", num) 
            }
        })
        .map(|output| { println!("{}", output); output })
        .collect::<Vec<_>>();
}

在 Online Rust Playpen 中打开此代码片段 >>

我们不仅可以使用 闭包 和函数式方法调用来模仿 JavaScript 的函数式风格,而且由于 Rust 强大的 模式匹配,我们甚至可以稍微胜过 JavaScript 的表现力。

注意:Rust 的迭代器是惰性的,因此我们实际上需要 .collect() 调用(称为消费者)才能使它工作——如果您省略了它,上述代码将不会执行。查看 Rust 手册的 消费者 部分以了解更多信息。

选择一个项目

您渴望深入研究 Rust 并准备开始做一些很酷的事情。但要构建什么?您可以从小处着手——编写一个小型应用程序或一个小库,并将其上传到 crates.io。人们已经在 Rust 中创建了许多激动人心的项目——从宠物类 *nix 内核到嵌入式硬件项目,从游戏到 Web 服务器框架,证明唯一的限制是您的想象力。

如果您想从小处着手,但在学习 Rust 编程的复杂性的同时贡献一些有用的东西,可以考虑为 GitHub 上已经蓬勃发展的项目做出贡献(甚至为 Rust 编译器本身做出贡献——它也是用 Rust 编写的)。
或者,您可以先重写生产应用程序中的一部分,然后试验结果。Rust 的 外部函数接口 (FFI) 是作为 C 代码的直接替代品而设计的。只要付出很少的努力,您就可以用 Rust 实现的相同代码替换应用程序的一部分。但互操作性并不止于此——这些 C 绑定可以双向使用。这意味着您可以在 Rust 应用程序中使用所有标准 C 库——或者(由于许多编程语言都带有标准 C 接口库)在 Rust 和 Python、Ruby、node.js 甚至 Go 之间进行来回调用!

观看 Dan Callahan 的演讲 “我的 Python 有点 Rust 味,了解如何将 Rust 与 Python 连接起来。

几乎没有哪种语言不能与 Rust 协同工作:您想 将 Rust 编译成 JavaScript在代码中使用内联汇编 吗?可以!

现在出去构建一些很棒的东西吧。

请不要忘记——Rust 不仅仅是一种编程语言,它也是一个社区。如果您遇到困难,请不要犹豫,在 论坛IRC 上寻求帮助。您也可以在 Rust Reddit 上分享您的想法和创作。

Rust 已经很棒了——正是像这样的人将会让它变得更加

关于 Szmozsánszky István

大多数人称他为“Flaki”,JavaScript fiddler、Firefox OS 爱好者和物联网黑客。他是一名服务工作者倡导者,为开放式 Web 应用程序与原生应用程序的同等性而奋斗——同时对微控制器、NFC 和硬件黑客作为副业感到非常着迷。

Szmozsánszky István 的更多文章…


6 评论

  1. Ivan

    嗨,

    好文章。

    我只是错过了——总是受欢迎的——基准测试;)

    顺便说一下:Rust 手册的链接已损坏。

    2015 年 5 月 15 日 下午 6:34

  2. incon

    运送到澳大利亚的 T 恤要 16 美元 :(

    2015 年 5 月 15 日 下午 7:53

  3. Redge

    几个月前我偶然发现了 Rust,在我问了自己很多次“为什么我要学习另一种语言?”之后,Rust 已经成为我最喜欢的语言之一。这种伟大语言的奇迹得到了像本文这样的优秀文章的补充,这些文章将向那些可能对 Rust 有所犹豫的程序员介绍该语言。

    跨平台、外部函数接口等等,使 Rust 成为一种理想的语言。我的第一种语言是汇编程序(因为当时我只能买到宏汇编程序),后来我学习了 C/C++、Java 等等(Python、Go)。

    我喜欢 Rust 提供语言集成,而不是为给定应用程序选择一种语言而不是另一种语言,正如本文清楚地展示的那样。感谢分享。

    2015 年 5 月 16 日 上午 4:11

  4. Havi Hoffman [编辑]

    @Ivan,感谢您的提醒。已修复!

    @incon,对于运费价格感到抱歉。:-( 将货物运送到国外的成本很高。

    2015 年 5 月 18 日 上午 10:07

  5. gialloporpora

    我们已经将这篇文章翻译成意大利语
    http://bit.ly/1K0PjF1

    我认为 C 代码中存在一个小问题,可能是 HTML 编辑器将其删除了:标准输入/输出库的名称丢失了(我认为是 stdio.h)。

    2015 年 5 月 30 日 下午 1:41

  6. gialloporpora

    我们已经将这篇文章翻译成意大利语
    http://bit.ly/1K0PjF1
    我认为 C 代码中存在一个小问题,可能是 HTML 编辑器将其删除了,需要包含的库丢失了(我认为是 stdio.h)。

    2015 年 6 月 1 日 上午 3:48

本文的评论已关闭。