2018的Rust:更好用

自 2013 年年底以来,我一直有在使用 Rust 。4周前,我再次拿起 Rust ,该语言比我上次使用时(2016年5月)更加容易。我觉得这真的很刺激! 所以今天我想谈谈我为什么喜欢使用 Rust ,以及一些关于 Rust 在 2018 年可以拓展的领域的想法! (作为对社区博文帖子的回应)

我和 Rust

我是一个中级的 Rust 程序员(绝对不是高级的!),现在我正在用 Rust 写一个分析器,到目前为止大概有 1300 行代码。2013 年我曾用 Rust 写了一个 400 行的“操作系统”(是一个简单的键盘驱动程序)。

尽管在 Rust 方面我没有太多的经验(使用 Rust 还不到10周),但我认为 Rust 已经能够让我做很多非常棒的事情!例如——我正在用 Rust 写的这个 Ruby 分析器,只需要有访问 PID、内存映射和从进程中读取内存的能力,就可以调用任意 Ruby 程序中的堆栈进行跟踪。它已经可以工作了!虽然距离发布第一个版本还有一些工作需要做,但是在我的笔记本里它在 35 个不同的 Ruby 版本都可以运行(从1.9.1到2.5.0)!即使 Ruby 程序的符合被剥离,也没有调试信息,但它确实是有效的!

对我来说,这真是太神奇了,如果没有 Rust 的话,我真的不认为能这么快就做到这一步。

Rust 的编译器比 2016 版更好用

对于一个不经常使用 Rust 的用户来说,编译器的改进是一件非常 Cool 的事情!我上一次使用 Rust 是在2016年的5月份(同一个 Ruby 分析器项目)。

在2016年,从我使用 Rust 的经验来说,它的编译器是非常难用的,在 RustConf talk in 2016 中,我说到:

我在很多时候对 Rust 的编译器感到沮丧,但我仍然喜欢它,因为它让我做了一些我可能不会做的事情。

现在我不用再忍受 Rust 的编译器了。并不是因为我对 Rust 更了解了(我还没有!),而是因为 Rust 的编译器更好用了

这当然不是魔法,而是因为大量的工作在 Rust 的贡献者。在 Rust 的 2017 路线图中,他们宣布 2017 年要将重点放到生产力上,并表示:

对生产力的关注似乎与 Rust 的其他目标不一致。毕竟,Rust 关注的是可靠性和性能,很容易想象,实现这些目标会迫使其他地方妥协——就像学习曲线,或者开发者的工作效率。“Fighting the Borrow Checker” 是 Rustaceans 首次获得成功的必经之路?是否要移除故障和复杂的需要掩饰的安全漏洞和性能障碍?

我们用 Rust 的处理办法一直都是权衡再权衡,正如我们在这个博客上谈论过的各种支柱中所体现的:

我喜欢这种方式(“我们将使它使用起来更方便同时不影响可靠性和性能”), 我觉得他们真的付出了努力。

但是,当我在说器编译器时,我总是很小心的说“更容易”而不是“容易”——我想 Rust 在如何变的“容易”是有一些局限性的!当然,关于 Rust 的问题(像编译时的线程安全保障一样),从根本上来说,你需要仔细考虑你的程序到底在做什么。所以我不期望或者希望 Rust 能够像“ Python 一样容易”之类的。

到目前为止编译器提示的非常棒的错误信息的例子

为了展现 Rust 的编译器是如何的好:下面是我这一两天得到的关于编译器提示的错误消息的真实例子。我找到这些例子仅仅是通过往上翻阅我的终端内容。

这是第一个例子:

error[E0507]: cannot move out of borrowed content
  --> src/bin/ruby-stacktrace.rs:85:16
   |
85 |         if let &Err(x) = &version {
   |                ^^^^^-^
   |                |    |
   |                |    hint: to prevent move, use `ref x` or `ref mut x`
   |                cannot move out of borrowed content

这个错误提示是非常有用的!!我按照提示操作:我将 ref x 用 x 替代,然后我的程序编译通过!!现在这种情况是经常发生的——我只是按照编译器告诉我的方式去做,然后正常运行!

下面是另一个简单的错误消息提示的例子:我无意中忘了给 Err() 传参数。它确切指出了出现问题的代码我觉得这点非常好。

error[E0061]: this function takes 1 parameter but 0 parameters were supplied
   --> src/bin/ruby-stacktrace.rs:154:25
    |
154 |             if trace == Err() {
    |                         ^^^^^ expected 1 parameter

最后一个非常棒的例子:我忘了输入 Error 正确的类型。Rust 给出了4种我可能会在这使用的非常有用的类型!(我想用的 failure::Errror 也在列)。

error[E0412]: cannot find type `Error` in this scope
   --> src/lib.rs:792:84
    |
792 |     ) -> Result<Box<Fn(u64, pid_t) -> Result<Vec<String>, copy::MemoryCopyError>>, Error> {
    |                                                                                    ^^^^^ not found in this scope
help: possible candidates are found in other modules, you can import them into scope
    |
739 |     use failure::Error;
    |
739 |     use std::error::Error;
    |
739 |     use std::fmt::Error;
    |
739 |     use std::io::Error;

依然有不好用的地方(他们正在解决)

当然,有些时候,Rust 并不能呈现出我想要的结果。例如: ruby_stacktrace::address_finder::AddressFinderError 这个类型,是表达 Error 的原因。所以当 Error 出现时,我应该能够返回一个 AddressFinderError ,对吧?错!

先不抱怨 Rust :

Compiling ruby-stacktrace v0.1.1 (file:///home/bork/work/ruby-stacktrace)
error[E0308]: mismatched types
  --> src/bin/ruby-stacktrace.rs:86:20
   |
86 |             return version;
   |                    ^^^^^^^ expected struct `failure::Error`, found enum `ruby_stacktrace::address_finder::AddressFinderError`
   |
   = note: expected type `std::result::Result<_, failure::Error>`
              found type `std::result::Result<_, ruby_stacktrace::address_finder::AddressFinderError>`

我知道怎么样处理这个问题:我可以通过返回 OK 来绕过这个问题(版本?)并且我的代码可以编译通过。但是编译器不会告诉我怎么样去解决这个问题,而且不能给我任何解决这个问题的清晰的线索。

但是!!!基本上每一次我都会遇到这样一个让人恼怒的问题,我问过 Kamal 这个问题(他用 Rust 的时间比我长),他说“是的,Rust 确实有一个 RFC ,人们正在积极的解决这个问题!”

下面2个具体的让人恼怒的例子已经是公认的 RFCs (这意味着这些问题正在解决中!):

  • 一个让人恼怒的事情是,有时你需要在你的代码中插入括号来进行编译。还有一个被接受的 RFC 称为非词汇生命周期,基本上使变量生命周期变得更加智能化!
  • 当我使用引用时(经常使用!!)我经常遇到一种状况,编译器告诉我需要在某处添加或删除一个符号(类似于我给出的第一个编译器错误消息提示)。在引用上更好的人机工程匹配模式这个公认的 RFC 让使用引用更加便捷而且不损耗任何的可靠性和性能!这很棒!竞争工程学特性已经出现在 Rust 的 nightly 构建版本中!

Rust 继续投入大量的时间去研究像这样的人类工程学的问题,这让我感到非常高兴。这都是个别比较小的让人恼怒的问题,但是,当大量的问题被修复时,我认为这将会对使用 Rust 的体验上带来巨大的正面效果。

简单的权衡:clone()

我喜欢 Rust 的另一个原因是它能够用一些简单的方法来避免一些复杂的操作。例如!!在我的程序里有一个 get_bss_section 的方法。这个方法是非常简单的——它只是遍历了 ELF 文件的二进制部分并返回这部分名为 .bss 的头。

pub fn get_bss_section(elf_file: &elf::File) -> Option<elf::types::SectionHeader> {
        for s in &elf_file.sections {
            match s.shdr.name.as_ref() {
                ".bss" => {
                    return Some(s.shdr.clone());
                }
                _ => {}
            }
        }
        None
    }
}

在编译器中我遇到了一大堆有关权限的问题,我真的不知道要怎么解决。所以我做了一个简单的权衡!我只用了 clone()来拷贝内存,这样问题就解决了。现在我可以将注意力放在实际的程序逻辑中!

我认为在开始使用 Rush 时,用这种方法(会让程序更容易编写并牺牲一点性能)去权衡是非常成功的。我最喜欢的是这个独特的权衡是显式的。我可以在程序里查找到每一个使用了 .clone() 地方并且检查它们 —— 函数被调用的太多了吗?我是否应该担心?当我检查到代码中使用了 .clone() 的地方在程序起始处的函数中,并且只被调用了一次或两次。我可能需要以后对它们进行优化。

Rust 的 crate 生态非常棒

在我的程序中,我解析了 ELF 二进制文件。事实证明,有一个 crate 可以做到这一点:elf crate

现在我正在使用 elf crate 来完成这类事情。但也存在 goblin crate ,它支持 Linux(ELF)、Mac(Mach-o)和 Windows(PE32)的二进制格式! 我可能会在某个时候切换到 goblin 。这些库的存在,以及他们是有据可查、易于使用,令我喜爱有加!

另一个我喜爱的 Rust crate(通常意义上的 Rust )是 —— 我觉得它们通常不会在它们所暴露的概念的基础上增加不必要的抽象。elf crate 里的结构就像 —— Symbol,Section,SectionHeader,ProgramHeader …… 等 ELF 文件中的概念!

当我发现一件我从来没有听说过的奇怪的事情,我需要使用它时(在程序标题 vaddrfield ),它就在那里! 它被称为 vaddr ,在 C 结构中也被这样叫。

Cargo 令人惊叹

Cargo 是 Rust 的包管理器和构建工具,它非常棒。我认为它是相当值得被大家知道的。这一点我感受颇深,因为我最近一直在使用 Go —— Go 有很多我喜欢的东西,但 Go 的软件包管理真的非常痛苦,而 Cargo 是很容易使用的。

我在 Cargo.toml 文件中的依赖关系看起来像这样。很简单!

[dependencies]
libc = "0.2.15"
clap = "2"
elf = "0.0.10"
read-process-memory = "0.1.0"
failure = "0.1.1"
ruby-bindings = { path = "ruby-bindings" } # internal crate inside my repo

Rust 给我全部控制权 (就跟 C 一样!)

在 Rust 中,我可以控制程序的每一个方面 – 我可决定究竟是选择什么系统调用,分配哪类内存,让程序休眠多少微秒等等一切东西。我觉得可以在 C 中做的任何事情,都可以在 Rust 中做到。

我真的很喜欢这样。对于大多数编程任务来说 Rust 并不是我的首选语言(如果我想编写一个 Web 服务,从个人角度我可能不会使用 Rust 。如果你对 Rust 的 Web 服务感兴趣的话,看看 are we web yet 一文)。我觉得 Rust 就像我的超级英雄语言! 如果我想做一些奇怪的系统魔法级的东西,我知道在 Rust 中将是可行的。也许并不容易,但绝对可行!

bindgen &宏是让人惊艳的

我已经写了关于这些的博客文章,但在此我想再谈谈!

我使用 bindgen 为每个我需要引用的 Ruby 结构(跨越 35 个不同的 Ruby 版本)生成 Rust 结构定义。这有点魔幻吗?就像我刚刚指向的一些内部 Ruby 头文件(从我的本地克隆的 Ruby 源代码)中那些我想提取的结构定义,告诉它我感兴趣的 8 个结构体,然后它完成任务了。

我认为 bindgen 可以让你和用 C 语言编写的代码很好地互操作,这真是不可思议。

然后我使用了宏(参见:我的第一个 rust 宏),并写了一堆代码引用这 35 个不同的结构体版本,以确保我的代码可以在所有这些版本中正常工作。

而当我引入一个新的内部 API 变化的 Ruby 版本(如2.5.0)时,编译器会说:“嘿,你的旧代码可以在 Ruby 2.4 中的结构体上工作,但现在不能编译了,你必须处理这个问题。”

2018 Rust 的目标是什么?

New Year’s Rust: A Call for Community Blogposts 中,Rust 核心团队被要求在社区博客上写关于 Rust 在 2018 的目标。我读了很多这样的帖子。我最喜欢的两个帖子分别是 Aaron Turon 的子:2018 年的Rust:一个人的视角和 withoutboat 的 2018 年我的 Rust 的目标

我真的很喜欢 withoutboat 在博客结尾的方式:

当一个有高级语言开发经验的程序员开始使用 Rust 时,他们现在拥有的编程技术空间将大大提升。当系统编程知识广泛且容易获取时,当有兴趣的人能够掌握他们已经掌握的技能,并利用它开始修补那些以前似乎无法访问的领域,如操作系统、网络协议、加密技术或编译器时,我希望能看到各种程序的出现。

下面是我对这个目标的2个看法!

目标1: 号称“从此更易用的 Rust ”的大版本发布

我认为 Rust 有极大的能力赋予人们编写有趣的和难度较高的程序,而这些程序是没有 Rust 的情况下不可能编写的。像是 profilers!网络软件!调试器!操作系统!

但对于使用 Rust 的人来说,我认为告诉他们 Rust 现在更易使用非常重要。

我和一位狂热的 Rust 程序员(Kamal)住在一起,他非常关注 Rust 语言的发展,并且我通常和他探讨 Rust 。但我没有意识到 Rust 的可用性上竟有了如此多的改进,直到我再次使用它。所以,我想大多数其他人也没有意识到 🙂 。

我认为 Rust 有点难上手的名声在外。扭转印象的确很难!但我认为如果能像 Firefox Quantum 那样发布一个(不一样的)版本非常棒,然后说:“嘿,当你最后一次试用 Rust 的时候,你对 Rust 编译器感到沮丧吗?我们努力了很多!再给我们一次机会吧!”

目标2:在 rust-lang.org 上解释啥是 Rust 编程语言

我觉得告诉哪些人和什么项目用 Rust 是个好的选择还是有点难(尤其是新手!)。就像 —— Rust 真的很棒,并且对于很多不同类型的人来说都是很酷的,但是 Rust 依旧是有点专业的,可事实上并不是每个人都这么认为的。Rust 适合谁?(friends of rust page 是我知道的最好的资源)

Rust 有很好的包容性(“Rust 对你很合适!”)但是IMO “ Rust 适合的 10 种特定的人群”比通用的包容性声明更有用。

下面有一些建议对于如何回答“ Rust 适合谁?”这个问题:(这些并不意味着 Rust 是专用的,但是他们的目标的相当明确的!我认为 Rust 可能适合所有这些人,还有更多的人:))

Rust 适合那些想用 C/C++ 进行编程但发现这些语言太难学的人。

Rust 适合那些构建大型的、复杂、性能敏感系统软件项目的人。火狐的有很大一部分就是由 Rust 编写的,而且 Rust 对于改进火狐的性能有重要的贡献。

Rust 适合熟练使用 C/C++ 的人,他们可以更好的确保在未定义行为上的编译时间。

Rust 适合想要编写安全系统的人,这对于缓冲区溢出和其他未定义的行为是安全的。

Rust 适合学生和那些对学习系统内容有兴趣的人。许多人通过 Rust 了解了诸如操作系统开发之类的知识。

Rust 适合嵌入式程序员,他们想要高级语言的功能,但又需要编译成像 C 代码那样小而有效的代码

Rust 适合公司!有很多关于人们在 Rust 上如何建立企业的故事。

Rust 适合那些想构建 Rust 编程语言的人。我们希望你为 Rust 的语言作出贡献。

另外 – 谁不是 Rust 的目标群体呢? Rust 想要获得却没有获得的组织有哪些? 什么样的人是 Rust 明确不提供服务的?

我认为 Rust 能够为不同群体提供服务是令人兴奋的 – 就像我认为 Rust 是为那些希望能够编写 C/C++ 应用但他们发现这类语言很难的人所设计的那样,而且 Rust 是为 C/C++ 专家提供的,他们可以从中获得更多的系统编程语言。

就这样! 2018年,我对 Rust 感到非常兴奋,并且在接下来的10周里我将继续使用 Rust 进行工作!

本文文字及图片出自 OSchina

余下全文(1/3)
分享这篇文章:

请关注我们:

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注