From Wikipedia:Rust是一个由Mozilla主导开发的通用、编译型编程语言。它的设计准则为“安全,并发,实用”,支持函数式,并发式,过程式以及面向对象的编程风格。Rust的语法与C++相似(我不这么认为),但它在维持高性能的情况下能保证内存安全。

Rust语言官网:Rust 是一种系统编程语言。 它有着惊人的运行速度,能够防止段错误,并保证线程安全。

Rust连续3年在stackoverflow 年度调查中被评为最受喜爱的编程语言(the most loved programming language。)

扩展阅读:为什么我说Rust是靠谱的编程语言 by Liigo

特性

无 GC & 无 VM & 极小运行时 & 完善的包管理 & 高效 C 绑定

Rust是编译型强类型语言,不是JavaScript、Python这种动态类型语言,也不是Java、C#这种需要虚拟机的语言,也不是Golang这种需要GC的语言。Rust的运行效率与C++相同甚至更好,而Rust的设计目标就是替代C、C++。

在过去,如果向要写高性能,高实时性,或者接近底层的大型程序,我们几乎只能选择C、C++。出于开发效率的考虑,应用软件很少用汇编开发,使用纯C的也很少,而操作系统等底层软件更多的使用C语言,并伴随少量的汇编,几乎不会使用C++(似乎只有Google的Fuchsia使用阉割后的C++)。C++从1983年诞生以来,从C with classes,到C with template,再到C++11,编程范式几经改动,存在许多奇技淫巧,却始终摆脱不了C的影子,就像一辆50年前的跑车经过多次改装,从里到外都改成了21世纪的超跑,虽然开着炫酷,但很容易出故障。而Rust没有历史包袱,其设计上比c++科学的多,没有C++满身补丁的痕迹。

Rust的运行时很小,为了底层开发甚至操作系统开发,减小平台移植难度,Rust在开发阶段砍掉标准库std中的许多重型功能,并建设了Rust包管理软件cargo,包托管网站crates.io,包文档的托管网站docs.rs,其中的许多包的质量和std一样高,使用也很方便。但Rust的std又不像C的库一样缺少基础设施,hashmap、统一api的多线程、网络功能都有。Rust还允许程序不使用std库,只使用平台无关的core库,以进行嵌入式和操作系统开发,也允许手动选择内存分配器(molloc)。

Rust设计上无GC、VM让其很容易调用C代码,但Rust更近一步,实现了Rust与C、C++的高效双向调用。许多语言只能自己调用C库,并且有效率上的问题,但Rust可以和C无缝交流,既可以在Rust项目中使用C库,也能在C项目中使用Rust库。此外Rust也能和python,JavaScript,Ruby进行模块调用。

trait & 泛型 & 模式匹配 & 闭包 & 基于语法的宏 & 零开销抽象

Rust具有泛型,像C++一样,泛型函数会编译成不同类型的版本,而不是像Java一样抹去类型信息。Rust不像Java那样宣传everything is class,没有继承,有结构体并能为其实现方法。Rust 有像Java中的interface一样作用的trait,指定一个结构体需要实现哪些方法,并可以用在泛型中。Rust使用属性来自动实现一些trait,例如,

#[derive(PartialEq, Clone)]
struct Foo<T> {
    a: i32,
    b: T,
}

编译器会为这个struct自动实现PartialEq, Clone。

impl<T: PartialEq> PartialEq for Foo<T> {
    fn eq(&self, other: &Foo<T>) -> bool {
        self.a == other.a && self.b == other.b
    }

    fn ne(&self, other: &Foo<T>) -> bool {
        self.a != other.a || self.b != other.b
    }
}

impl<T: Clone> Clone for Foo<T> {
    fn clone(&self) -> Foo<T> {
        Foo {
            a: self.a.clone(),
            b: self.b.clone(),
        }
    }
}

Rust广泛的使用了枚举类型,Rust的枚举与C不同,更像是来自函数式编程语言的代数数据类型。最常见的两个枚举就是OptionResult

enum Option<T> {
    Some(T),
    None,
}

Option代表了一种非常常见的场景:一个值要么是一个值,要么什么都不是。许多语言都含有空值null,但程序并不能像对待非空值那样对待空值。快速排序算法的作者,图灵奖得主,同时也是NULL的作者Tony Hoare认为他发明的空指针是一个十亿美元的错误。我们要么到处使用xxx!=null,要么放弃治疗,自以为null不会出现,并放任程序可能某一天不知为什么的崩溃。Rust的Option就能解决这一表达问题。我们可以使用Option的方法提取到Some中的数据T,并根据需要在遇到None时崩溃unwrap()、返回默认值unwrap_or(T)、或执行闭包中的回调函数let x = some_u8_option.unwrap_or_else(|| 2 * y),|| 2 * y是一个闭包,代表没有传入的参数,返回2*y,其中y的值应该出现在上文中,或者使用模式匹配:

match some_option {
  Some(val) => println!("{}",val),
  None => println!("nothing find:("),
}

这样,编译器与程序员都可以清楚的知道何时会出现这种情况,并选择一种解决方案。

enum Result<T> {
    Ok(T),
    Err(Error),
}

Result则代表了另外一种常见情况:异常处理。Rust不使用try,catch这种处理方式,而是选择使用返回Result枚举,如果返回的是Ok,那么就Ok,如果是Error,那就需要处理错误,或者将错误再次返回——效果和try相同。

fn read_username_from_file() -> Result<String, io::Error> {
    let f = File::open("hello.txt");

    let mut f = match f {
        Ok(file) => file,
        Err(e) => return Err(e),
    };

    let mut s = String::new();

    match f.read_to_string(&mut s) {
        Ok(_) => Ok(s),
        Err(e) => Err(e),
    }
}

这么写很繁琐,Rust有专门的操作符来简化返回Err的语法

fn read_username_from_file() -> Result<String, io::Error> {
    let mut f = File::open("hello.txt")?;
    let mut s = String::new();
    f.read_to_string(&mut s)?;
    Ok(s)
}

以上两个函数的功能相同。你可能注意到函数最后没有使用return,因为Rust的语句是具有返回值的,我们无需在最后一行写return。_代表我们对这个表示符的名字不感兴趣。

Rust也可以使用let进行匹配,例如let (x,y)=(1,2),或者if let Some(3) = some_u8_value { println!("three"); } else { println!("not three"); }

Rust具有强大的宏系统。C语言的宏是基于文本替换的,很容易出现替换问题,而Rust的宏是基于语法单元进行展开的。宏可以自动生成代码。例如

macro_rules! find_min {
    ($x:expr) => ($x);
    ($x:expr, $($y:expr),+) => (
        std::cmp::min($x, find_min!($($y),+))
    )
}

这个宏通过递归实现了寻找参数中最小值,普通的函数并不能接受任意个参数,而println!,pacnic!这样的宏就能接受任意多的参数。Rust的宏系统还没有完成,相关信息:RFC,Issue

避免段错误、悬垂引用、线程数据竞争,保证内存安全

上面的特性听起来像Rust是重新造C++的轮子,只是语法不同,但这个特性成为超越C++的关键。写过C或C++的一定遇到过内存安全问题,这些各种各样奇怪的指针问题,常常要调试数个小时甚至数天,有时程序莫名其妙的崩溃,有时程序运行正常但含有安全漏洞,许多漏洞都是缓冲区溢出等等内存不安全问题。C发明时,计算机处理能力有限,内存也很有限(比现在几块钱的单片机还差),当时的共识是程序员有能力管理好内存。然而几十年过去了,内存从比尔盖茨口中的“640k的内存能做任何事情”到了几个GB,程序员在面对几十万行的代码时还能管理好内存吗?

指针的存在就是为了访问指针指向的数据,由于C将内存数据的生命周期管理交给了程序员,因此C程序员必须手动使用指针、molloc和free维护数据的生命周期,然而人总会写出use after free的错误,因此Java、.NET、JavaScript、Python、Golang都使用垃圾回收算法来自动管理内存,然而,运行时也是人写的,Java runtime、IE、Flash这些平台运行时本身就是BUG大户,占据0day安全漏洞数量排行前3的地位。C++11之后,智能指针等特性让内存安全问题更不容易出现,但由于兼容性等原因,C++始终不能放弃裸指针的使用,也就不能保证安全,只能减少问题。

Rustc禁止在unsafe外使用裸指针,也禁止产生空指针。Rust使用一套简单的概念,让Rustc编译器维护了类型的生命周期的问题。首先,Rust中的变量只有标识符的作用,不包含值,只有将资源绑定到标识符上变量才可以直接使用。例如let x:i32=100,就是将100这个i32的值绑定到x这个标识符上,如果我们只声明let x:i32,再试着打印x,编译器就会报错,而许多语言会使用默认值甚至是内存里的随机值。Rust的作用域为大括号级别的,如果局部变量离开作用域,会导致其绑定的资源销毁(drop),这是编译器自动插入并执行的,类似C++的析构,但我们不需要手动编写drop函数,这是通过所有权系统实现的。一个资源,无论是栈上的还是堆中的,都只能绑定到一个变量,即这个变量拥有了这个资源的所有权。只有拥有资源所有权的变量离开作用域了,资源才会被销毁。一个资源对应一个变量,Rustc在编译时就可以确定所有资源的生命周期,即从其创建到最后一个拥有它的变量离开作用域,这套系统也被称为RAII(Resource Acquisition Is Initialization,资源获取就是初始化),如果我们在生命周期外使用资源就会在编译时报错,而C++则是运行时。

Rust中的绑定默认是immutable的,如果需要修改值,需要在声明时使用mut关键字,例如let mut x:i32=100。Rustc会追踪声明为mut但没有修改过的变量,并给出warning。

有时候我们会把一个值赋给另一个变量,例如let y=x(Rust会自动判断类型),这时x的所有权会转移(move)给y,我们再试图访问x就会报错。不过,对一个类型实现了clone这个trait后,这个类型的数据在move时会clone自己再赋值到新变量上,原变量仍然可以访问,大多数栈上的原始类型都实现的clone。

如何在不获得所有权的情况下访问一个不能clone的资源呢,Rust提供了借用。借用(borrowing)也叫引用(reference),但与其他语言不同,在同一个作用域中,只能出现一个可变引用,如 let y = &mut x(x必须是可变的),或多个不可变的引用,如let y = &x; let z = &x。借用不会转移所有权,因此函数如果不想转移所有权,就必须将参数声明为引用。借用的生命周期必须处在所有者的生命周期内,否则会出现编译错误。存在借用时,所有者不能更改资源,也不能转移所有权。整个借用系统的检查是在编译时完成的。

有些时候,Rustc不能推断出函数的参数与返回值之间的生命周期关系,例如fn foo(x: &str,y: &str) -> &str,Rustc不知道返回值来源于x还是y,会指出:

error: missing lifetime specifier [E0106]
fn foo(x: &str, y: &str) -> &str {
                            ^~~~

我们可以修改为fn foo<'a>(x: &'a str, y: &'a str) -> &'a str'a代表生命周期,表示如果x,y都在a这个生命周期下,返回值就一直有效,或者指明fn foo<'a, 'b: 'a>(x: &'a str, y: &'b str) -> &'a stry的生命周期更长,因此返回值的生命周期至多为x的生命周期。

生命周期语法在新手看来非常复杂,相比起C++手动管理生命周期,写几个标记并不是难事。Rust开发人员正在努力的减少无法推断的生命周期类型。

有时候,Rust的安全检查阻止我们实现一些功能,例如编写操作系统的内存管理模块,我们需要指针这样的功能。Rust含有unsafe关键字,在unsafe的代码块下,Rust额外可以:

解引用裸指针 调用不安全的函数或方法 访问或修改可变静态变量 实现不安全 trait

裸指针忽略借用规则,且允许为空,unsafe需要我们自行判断裸指针是否指向合法内存,需要手动free裸指针的内存。我们可以通过放弃Rust提供的安全保证换来对其他语言或硬件的访问。注意,unsafe不代表不安全,只是说内存安全需要由程序员维护。

C语言/C++发明时CPU还都是单核心,而现在CPU核心数越来越多,再加上超线程技术,应用程序势必要走向多线程,然而竞争的存在使得使用C++编写程序很容易出现bug。Servo是Mozilla开发的多线程并行浏览器引擎,也是唯一一个并行浏览器引擎,为什么是唯一一个呢,因为使用C++根本没用办法写出没有bug的并行浏览器来。(这里的并行指的是每一个网页的解析、渲染、运行是并行的,大多数浏览器是每个网页一个进程,但这个进程的并行度不高) Servo的CSS渲染引擎成为Firefox57的默认引擎后,其CSS渲染性能提升100%以上(技术细节)。

Rust通过Send+Sync这两个trait处理多线程问题。这两个trait没有定义任何方法,Send表示为如果T:Send,将T move到另一个线程,能保证所有权能安全的转移到新的线程并与原线程解耦,不会使原线程use after free或可以同时访问同一块内存等内存安全问题。Sync表示为如果T:Sync,将&T send到另一个线程,不会出现竞争问题。显然,T:Sync等于&T:SendSync+Copy=Sendsendsync不能手动实现,基本上所有的原始类型都是Send+Sync的,由原始类型与结构体、枚举组合成的类型也是Send+Sync的,而且是自动实现的,无需写任何代码。但标准库中有一些类型不是SyncSend的,它们的底层使用了unsafe的Rust(按照Rust的内存安全措施写不出这些类型)。裸指针既不是Send也不是Sync,手动实现Send和Sync需要unsafe块,但我们可以指定一个类型为!Send!Sync,无需unsafe。尽管Rust标准库和第三方包中使用了许多多线程API,但其中的安全性都是基于这套系统的。

大事件

  • 2006年Mozilla员工Graydon Hoare基于OCaml发起了Rust项目.
  • 2009年,Mozilla开始赞助这个项目。
  • 2010年,Rust编译器源码从OCaml转到Rust,次年完成自举,编译器被命名为Rustc,后端为LLVM。
  • 2012年,Rust首个大型项目,由mozilla和三星发起的Servo浏览器引擎开始编写,其目标是创造一个安全的并行浏览器引擎。Rust与Servo项目相互发展,共享一部分开发者。
  • 2012年,Rust 0.1发布,基本语言功能完善。
  • 2014年,分离core库与std库,以面向嵌入式与操作系统开发。
  • 2015年,Rust放弃绿色线程与垃圾回收库,删除运行时库。
  • 2015年,经过12个0.x,2个alpha版本,Rust1.0.0发布,这3年内每个版本改动均在一千以上。Rust承诺在2.0之前不破坏API兼容性。
  • 2016年,Servo发布预览版0.0.1。
  • 2017年,Servo部分代码进入Firefox正式版,如CSS渲染引擎Stylo

著名项目

使用Rust的商业项目见Rust官网的页面