Rust语言速成

学习Rust最好的方式,就是拿它写个OS。 ——作者

Rust语言独具特色,然而学习曲线十分陡峭。在你享受到它为系统编程带来的好处之前,很可能直接被它编译器严格的检查机制所劝退。

为了帮助大家尽快地上手Rust,这里简要列出了我个人认为合理地学习顺序,供大家参考。这里我们假设读者已经具有一定C语言编程经验。

存活阶段

  • 工具链基础使用方法

  • Rust基础语法:

    • 变量、函数、数据类型、控制流

    • 数组、字符串、结构体、元组、枚举

  • unsafe:指针操作

至此,你应该能够用Rust写等价的C代码了!

入门阶段

  • Rust内存模型:从此告别SegmentFault

    • 可变性

    • 所有权,移动语义

    • 生命周期和借用

  • Rust高级语法:现代语言特性

    • 模式匹配

    • 闭包/lambda表达式,函数式编程

  • 面向对象:Better C with Class

    • 成员变量

    • 接口:Trait,Trait对象(动态分派)

    • 泛型(静态分派)

  • 核心库:你要的轮子,都在这里

    • Option、Result、错误处理机制

    • 容器类:Vec、VecDeque、BTreeSet、BinaryHeap……

    • 迭代器、链式调用

    • 堆分配,智能指针:Box、Rc、Arc……

    • 消灭unsafe:运行时借用检查RefCell

    • 了解其它常用Trait

  • 项目管理:工程化与模块化

    • 模块系统,文件层次

    • 包管理系统,项目配置文件Cargo.toml

至此,你应该开始体会到Rust为系统编程带来的便利了!

高级阶段

  • 反射

  • 安全并发机制

  • 与C语言互操作

  • 内联汇编

Rust样例

这里给出一些需求的最简单实现。代码位于:RustSamples

目前包括以下功能样例:

  • Rust调用C

  • C调用Rust

值得一提的特点

大量泛型

如果你阅读过一些Rust代码就会发现,泛型这一特性被广泛运用,频率高于其它任何一门常用语言。在C++中,泛型/模板是晦涩和强大的代名词,而在Rust中,它平凡而常见。

其实,Rust中的泛型大部分只是为了解决一个简单的需求——调用接口

举个例子,我们要定义一个接口I,并在一个函数中使用它。在C++中,我们会使用基类指针

abstract class I {
    int func();
}

int call_func(I* p) {
    return p->func();
}

在Rust中其实也有类似的事物——Trait对象

trait I {
    fn func(&self) -> i32;
}

fn call_func(p: Box<I>) -> i32 {
    p.func()
}

但它并不常用。我们注意到p的类型是Box<I>,意味着传入对象必须是分配在堆上。如果把这里改成I,就会出现以下错误:

the trait bound I: std::marker::Sized is not satisfied [E0277] I does not have a constant size known at compile-time

Rust文档中也具体说明了Trait对象的使用条件——对象安全

由于以上方法用起来有诸多不便,因此我们常用泛型实现接口调用:

// T是一个实现了trait I的类型
fn call_func<T: I>(p: T) -> i32 {
    p.func()
}

// 当<>中的内容比较多时,
// 也可以把泛型约束放在外面
fn call_func<T>(p: T) -> i32 
    where T: I 
{ p.func() }

其实用泛型的方法实现接口调用在C++中也可以实现,但由于目前C++缺乏泛型约束(C++20的标准:Concept),导致传入错误类型后编译器会吐出一堆垃圾。很少有人使用。

使用泛型相比基类指针还有一个好处:性能更好。使用基类指针调用接口函数时,需要在运行时查虚表,而用泛型就能在编译时确定最终类型,节省一次查表开销,编译器还能做更多内联优化。前者称为动态分派,后者称为静态分派。但如果对象类型只能在运行时确定,那就必须用前者了。

没有继承

Rust作为一个面向对象语言,竟然没有继承?!

是的!很多新兴的编程语言都逐渐抛弃了继承这一特性,比如和它相爱相杀的Go。

回忆OOP里面诸多的设计原则,其中有一条就是:使用组合代替继承。其原因就是我们总会继承一些多余的东西,造成类间高度的耦合性。最早的C++最复杂,之后的Java做了一些简化(抽象出接口,只允许单继承),不过还不够彻底。到了Rust这代语言,继承被彻底抛弃,只保留了接口。

不过,Rust也提供了一个代替方案:Deref特性。使用方法是把“父类”当作“子类”的一个内部变量,Deref提供了一个语法糖来自动使用其“父类”的成员。

关于OOP和继承的讨论,推荐阅读这篇知乎回答,以及Rust文档中的继承

参考资料

Last updated