2018年伊始,系统编程语言Rust为何令程序员感到兴奋?

时间:2022-04-27
本文章向大家介绍2018年伊始,系统编程语言Rust为何令程序员感到兴奋?,主要内容包括我和Rust、Rust编译器比2016年更有用、极好的编译器错误消息的例子、仍然有恼人的部分(他们正在努力解决)、简单的权衡: .clone()、Rust的Crate生态系统非常棒、Cargo是惊人的、Rust控我所控(如C!)、bindgen和宏是惊人的、Rust的2018年目标应该是什么?、目标1: “Rust:现在更容易使用”、目标2:在rust-lang.org上解释Rust编程语言的用途、基本概念、基础应用、原理机制和需要注意的事项等,并结合实例形式分析了其使用技巧,希望通过本文能帮助到大家理解应用这部分内容。

自2013年年底以来,作者本人时断时续地会用Rust语言编程。4周前,再次用到Rust,语言比上次使用时更加容易(2016年5月)。这真的很令人兴奋!所以谈谈为什么现在喜欢使用Rust语言,以及几个关于Rust语言明年发展趋势的想法!

我和Rust

我是一个中级Rust程序员(绝对不是高级程序员!)。现在正在写一个Rust的剖析器,这是一个迄今为止大约有1300行Rust代码的软件。2013年,我用Rust语言写了一个很小的400行的“操作系统”(基本上是一个小键盘驱动程序)。

尽管没有太多的Rust经验(频繁地使用它不到10个星期),Rust已经让我做了很多很棒的事情!就像:我正在用Rust语言编写一个Ruby分析器,它只通过访问其PID、内存映射以及从进程读取内存的能力,可以从任意Ruby程序中提取Ruby堆栈跟踪。它已经可以工作了!要发布第一个版本,还有许多工作要做,但在我的笔记本电脑上,它可以在35个不同的Ruby版本 (从1.9.1到2.5.0)上使用!即使Ruby程序的符号被剥离并且没有调试信息,它也可以工作!

这感觉真是太神奇了,如果没有Rust,我真的不会这么快就完成。

Rust编译器比2016年更有用

作为偶尔使用Rust的用户,一件很酷的事情就是在编译器中看到了巨大的改进!最近一次是在2016年5月使用了Rust(用于相同的ruby profiler项目)。

在2016年,我使用Rust编译器的体验是它很难。在2016年RustConf访谈中,我说:

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

我不再对Rust编译器感到沮丧了。但这并不是因为学习了更多关于Rust的知识(我还没有!),这主要是因为编译器更有用了。

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

关注生产力可能与Rust的其它目标不一致。毕竟,Rust专注于可靠性和性能,很容易想象实现这些目标会迫使它在其它方面妥协,比如学习曲线或开发人员的生产力。“与借用检查员争斗”为新生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种我可能想在那里使用的不同的Error类型!(要的是failure::Error,它也在4种建议名单上!)。

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;

仍然有恼人的部分(他们正在努力解决)

当然,有些时候语言并不按大家想要的那样行事。例如,有这种类型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>`

可以通过编写return Ok(version?)来破解它,然后程序将被编译。但编译器并没有告知如何解决这个问题,也没有给出任何有关做什么的明确线索。

但!!!基本上每一次有这样恼人问题的时候,我问Kamal(写Rust比我更多的一个人),他说:“噢,那会有一个RFC(Request For Comment),或者至少人们正在积极地讨论如何解决那!”。

2个已经公认RFC的具体的恼人例子(这意味着它们正在被解决的路上):

  • 有一件恼人的事情就是有时需要在代码的一部分插入大括号来编译它。还有一个被称为非词汇生存期的RFC,可以让Rust变得更加智能化!
  • 当使用引用(总是!!)时,经常会遇到这样的情况:编译器告知需要在某处添加或删除一个&符号(就像上文给出的第一个编译器错误消息一样)。公认的RFC 更好的人机工程学模式匹配引用,使引用工作更容易,而不会牺牲任何性能或可靠性!非常酷!匹配人机工程学的特征已经在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(),将它们复制到内存,问题就消失了。这样可以回头专注于实际程序逻辑了!

在开始使用Rust的时候,能够做到这样的权衡(在牺牲一点性能的情况下使程序更容易编写)是非常好的。最喜欢这个特别权衡的地方是它是明确的。可以在程序里用.clone()搜索每个地方 ,并对它们进行审计—这个函数是否被调用了很多次?应该担心吗?刚刚查过程序中使用clone()的每一个地方,在程序开始的时候,只调用一次或两次。也许稍后会优化它们!

Rust的Crate生态系统非常棒

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

现在正在使用elf crate。但也有 goblin crate,它支持Linux(ELF),Mac(Mach-o)和Windows(PE32)二进制格式!可能会在某个时候切换。喜欢这些库的存在,它们文档齐全,使用方便!

另一个喜欢Rust Crate(一般是Rust)的地方是它们通常不会在它们所曝光的概念基础上增加不必要的抽象。Elf crate的结构就像Symbol,Section,SectionHeader,ProgramHeader,ELF文件中的概念!

当发现一个从未听说过的奇怪的事情需要使用时(程序头文件中vaddr的字段),它就在那里!它被称为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服务感兴趣的话,看看我们是否还在Web上)。Rust就像我的超级英雄语言!如果想做一些奇怪的系统魔法的东西,用Rust是可能的。也许不容易,但可能!

bindgen和宏是惊人的

已经写过关于bindgen和宏的博客文章,但想再谈谈它们!

使用bindgen为每个需要引用的Ruby结构(跨越35个不同的Ruby版本)生成Rust结构定义。这有点神奇?就像刚刚指出的一些内部Ruby头文件(从本地克隆Ruby源代码),想提取结构定义,告诉它我感兴趣的8个结构,它是有效的。

事实上,bindgen可以与C语言编写的交互操作这么好真是不可思议。

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

而当提出一个新的Ruby版本(如2.5.0)时,其内部的API发生了变化,编译器说:“嘿,你的旧代码与Ruby 2.4的结构一起工作,现在不能编译,你必须处理这个问题”。

Rust的2018年目标应该是什么?

新的一年的Rust:一个作为对社区博客帖子的回应,Rust核心小组要求社区撰写关于Rust的2018年目标应该是什么的博客帖子。我最喜欢的两篇博客帖子是Aaron Turon的:2018年的Rust:以人为本和withoutboats的 2018年我的Rust目标。

很喜欢withoutboats的博客帖子:

当具有高级语言经验的程序员开始使用Rust时,他们拥有技术编写的程序空间大大增加。当系统编程知识普遍且容易获取时,希望看到各种各样的程序类型,有兴趣的人可以在他们已有技能的基础上,利用Rust来开始修补曾经可能无法企及的领域,如操作系统,网络协议,加密或编译器。

下面是关于两个目标的想法!

目标1: “Rust:现在更容易使用”

Rust有一个巨大的机会来赋予人们编写有趣和困难的程序,而这些程序是没有Rust的情况下不可能写的。像个人资料!网络软件!调试器!操作系统!

但是对于使用Rust的人来说,告诉他们Rust现在更容易是很重要的。

我和一位热情的Rust程序员(Kamal)住在一起,他非常关注Rust语言的发展,我经常和他讨论Rust。直到再次开始使用它,才意识到Rust的可用性有了如此多的改进!所以,如果我没有意识到,我想大多数其他人都没有:)

Rust有难以学习的臭名。当然,这总是会有点难!但是如果有Firefox Quantum风格的版本像“嘿,当你最后一次尝试用Rust的时候,对Rust编译器感到沮丧吗?我们做了很多改进!再给我们一次机会!”会很好。

目标2:在rust-lang.org上解释Rust编程语言的用途

告诉哪个人,哪些项目用Rust是一个好的选择还是有点困难的(尤其是对于新手来说)。Rust真的很酷,它是为很多不同类型的人准备的,但它仍然是一个专门的东西,并不适用于每个人。那适合谁呢?(Rust页面之友是最好的资源)

Rust的包容性是很好的(“Rust可能适合你!”),但IMO的“有10种特定的人群适合用Rust”比一般的包容性声明显更有用。

这里有几条关于如何回答“谁适合用Rust?”这个问题的建议:(这些并不意味着是排他性的,但它们的目的是非常具体的! Rust可能适用于所有这些人,甚或更多 :))

  • Rust 适合那些希望能够编写C / C ++程序的人,但却发现这些语言无法接近。
  • Rust适用于构建大型,复杂,性能敏感的系统软件项目的人员。大部分Firefox都是用Rust编写的,Rust大大提高了Firefox的性能。
  • Rust是为C / C ++专家而设的,他们希望有更好的关于未定义行为的编译时间保证。
  • Rust适用于那些希望编写安全系统代码的人,这些代码可以避免缓冲区溢出和其它未定义的行为。
  • Rust 适用于对学习系统概念感兴趣的学生和人员。很多人通过Rust了解了操作系统开发等主题。
  • Rust 适合想要更高级语言的嵌入式程序员,但是需要编译成与C代码一样小和高效的代码。
  • Rust适合公司!这里有一些关于人们如何在Rust上建立业务的故事。
  • Rust适用于想要构建Rust编程语言的人。希望他们为Rust语言做出贡献。

另外,谁不适合 Rust?Rust 想要成为什么样的组织呢?什么样的人是Rust明确不想服务的?

Rust能够为不同群体提供服务是令人兴奋的,就像Rust适合那些希望能够编写C / C ++但是又发现这些语言很难的人们一样,Rust 也适合希望从他们的系统编程语言中得到更多的C / C ++专家。