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调用C
- C调用Rust
如果你阅读 过一些Rust代码就会发现,泛型这一特性被广泛运用,频率高于其它任何一门常用语言。在C++中,泛型/模板是晦涩和强大的代名词,而在Rust中,它平凡而常见。
其实,Rust中的泛型大部分只是为了解决一个简单的需求——调用接口。
举个例子,我们要定义一个接口I,并在一个函数中使用它。在C++中,我们会使用基类指针:
abstract class I {
int func();
}
int call_func(I* p) {
return p->func();
}
trait I {
fn func(&self) -> i32;
}
fn call_func(p: Box<I>) -> i32 {
p.func()
}
但它并不常用。我们注意到p的类型是
Box<I>
,意味着传入对象必须是分配在堆上。如果把这里改成I
,就会出现以下错误:the trait boundI: std::marker::Sized
is not satisfied [E0277]I
does not have a constant size known at compile-time
由于以上方法用起来有诸多不便,因此我们常用泛型实现接口调用:
// 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这代语言,继承被彻底抛弃,只保留了接口。
Last modified 4yr ago