引言

欢迎👏来到Rust Ocean。

读者定位

定位读者为懂得基本编程概念。为了简短忽略了基本概念,如果你觉得理解困难的话,考虑找更基础的书来看。

Rust是什么

Rust 是一门注重安全(safety)、速度(speed)和并发(concurrency)的现代系统编程语言。Rust 通过内存安全来实现上述目标,但不用垃圾回收机制(Garbage collection, GC)。

Rust解决了空指针问题,错误处理更可靠,大大增加了可靠性和对代码的信心❤️。

如果你看了我以上的简单介绍还不明真相的话,还在犹豫是否学习Rust的话,可以浏览下《爱上Rust》。在阅读前可以看看intorust的英文短视频介绍。

极简风格

本书采用极简风格,不求大全,目标是学完后有基本的了解。希望是轻松的方式,两三天就学完。

参考了《通过例子学Rust》《RustPrimer》两本书,更多内容请移步前往。

To English Readers

I wrote this in Chinese, so if some links are in Chinese, you can use Google Translate to help it.

第1章:基础知识

当你开始阅读的时候,说明你已经有一些兴趣了。
Rust入门非常简单,不需要任何配置。

可以去playground在线运行代码。本地安装就省略了,请参考引言中的参考书。

Rust三天简易教程(v1.0) - 图1

下面是hello world:

  1. // 这是注释内容,将会被编译器忽略掉
  2. fn main() {
  3. println!("Hello Rust World!");
  4. }

println! 是一个 (macros),可以将文本输出到控制台(console)。

使用 Rust 的编译器 rustc 可以将源程序生成可执行文件:

  1. $ rustc hello.rs

rustc 编译后将得到可执行文件 hello

  1. $ ./hello
  2. Hello Rust World!

有一个runner可以一步运行。

另外自带的cargo可以提供项目管理,在小项目里有介绍。

第2章:结构体、Trait

先看看结构体:

  1. // 单元结构体
  2. struct Nil;
  3. // 元组结构体
  4. struct Pair(i32, f32);
  5. // 带有两个字段的结构体
  6. struct Point {
  7. x: f32,
  8. y: f32,
  9. }
  10. // 结构体可以作为另一个结构体的字段
  11. #[allow(dead_code)]
  12. struct Rectangle {
  13. p1: Point,
  14. p2: Point,
  15. }
  16. fn main() {
  17. // 实例化结构体 `Point`
  18. let point: Point = Point { x: 0.3, y: 0.4 };
  19. // 访问 point 的字段
  20. println!("point coordinates: ({}, {})", point.x, point.y);
  21. // 实例化一个单元结构体
  22. let _nil = Nil;
  23. // 实例化一个元组结构体
  24. let pair = Pair(1, 0.1);
  25. // 访问元组结构体的字段
  26. println!("pair contains {:?} and {:?}", pair.0, pair.1);
  27. // 解构一个元组结构体
  28. let Pair(integer, decimal) = pair;
  29. println!("pair contains {:?} and {:?}", integer, decimal);
  30. }

Trait

  1. trait HasArea {
  2. fn area(&self) -> f64;
  3. }
  4. struct Circle {
  5. radius: f64,
  6. }
  7. impl HasArea for Circle {
  8. fn area(&self) -> f64 {
  9. std::f64::consts::PI * (self.radius * self.radius)
  10. }
  11. }
  12. fn main() {
  13. let c = Circle {
  14. radius: 1.0f64,
  15. };
  16. println!("circle c has an area of {}", c.area());
  17. }

这个程序会输出:

  1. circle c has an area of 3.141592653589793

{}, {:?}分别对应两个Trait:Display和Debug。

第3章:HashMap、数组

先看看HashMap的简单用法。

  1. use std::collections::HashMap;
  2. fn main(){
  3. let mut come_from = HashMap::new();
  4. // 插入
  5. come_from.insert("WaySLOG", "HeBei");
  6. come_from.insert("Marisa", "U.S.");
  7. come_from.insert("Mike", "HuoGuo");
  8. // 查找key
  9. if !come_from.contains_key("elton") {
  10. println!("Oh, 我们查到了{}个人,但是可怜的Elton猫还是无家可归", come_from.len());
  11. }
  12. // 根据key删除元素
  13. come_from.remove("Mike");
  14. println!("\nMike猫!\n");
  15. // 利用get的返回判断元素是否存在
  16. let who = ["MoGu", "Marisa"];
  17. for person in &who {
  18. match come_from.get(person) {
  19. Some(location) => println!("{} 来自: {}", person, location),
  20. None => println!("{} 也无家可归啊.", person),
  21. }
  22. }
  23. // 遍历输出
  24. println!("\n那么,所有人呢?");
  25. for (name, location) in &come_from {
  26. println!("{}来自: {}", name, location);
  27. }
  28. }

再看看数组:

  1. fn main() {
  2. let mut array: [i32; 3] = [0; 3];
  3. array[1] = 1;
  4. array[2] = 2;
  5. assert_eq!([1, 2], &array[1..]);
  6. // This loop prints: 0 1 2
  7. for x in &array {
  8. println!("{} ", x);
  9. }
  10. }

动态数组Vec

  1. fn main() {
  2. //创建空Vec
  3. let v: Vec<i32> = Vec::new();
  4. println!("{:?}", v);
  5. //使用宏创建空Vec
  6. let v = vec![1, 2, 3, 4, 5];
  7. println!("{:?}", v);
  8. //创建十个零
  9. let v = vec![0; 10];
  10. //创建可变的Vec,并压入元素3
  11. println!("{:?}", v);
  12. let mut v = vec![1, 2];
  13. v.push(3);
  14. println!("{:?}", v);
  15. //创建拥有两个元素的Vec,并弹出一个元素
  16. let mut v = vec![1, 2];
  17. let two = v.pop();
  18. println!("{:?}", two);
  19. //创建包含三个元素的可变Vec,并索引一个值和修改一个值
  20. let mut v = vec![1, 2, 3];
  21. v[1] = v[1] + 5;
  22. println!("{:?}", v);
  23. }

特别章:🔥所有权、借用和生命周期

所有权系统的这几个概念是Rust与众不同的地方。是安全基础,号称编译就不会崩溃。

所有权系统可以用借书来类比,先看所有权:

  1. fn main() {
  2. let a: String = String::from("xyz");
  3. let _b = a;
  4. println!("{}", a);
  5. }

这个代码在传统的语言是没问题的,这里编译会报错,因为发生了所有权转移。

再看借用:

  1. fn main() {
  2. let a: String = String::from("xyz");
  3. let _b = &a;
  4. println!("{}", a);
  5. }

改了一个字就是借用了,可以编译了。

再看生命周期:

  1. fn _foo<'a>(x: &'a str) -> &'a str {
  2. x
  3. }

这个就是有些奇怪的生命周期写法。有些简单的情况可以省略生命周期,更简洁。简单说,生命周期就是指定变量的存在时间吧,当编译器无法确定的时候,会要求程序员写生命周期。

小结

通过三个简单例子,介绍了三个概念,当然实际情况要复杂一些,不过基本思路差不多。

Rust正是通过所有权、借用以及生命周期,没有手动管理内存的安全性风险,也没有GC造成的程序暂停问题。需要付出的是一些时间去逐渐熟悉它们。

第4章:代码组织

模块的基本用法

  1. mod ccc {
  2. pub fn print_ccc() {
  3. println!("{}", 25);
  4. }
  5. }
  6. fn main() {
  7. use ccc::print_ccc;
  8. print_ccc();
  9. // 或者
  10. ccc::print_ccc();
  11. }

包crate是更大的单位。

真正我们在开发时,会大量用到外部库。外部库是通过

  1. extern crate xxx;

这样来引入的。

注:要使上述引用生效,还必须在 Cargo.tomldependecies 段,加上 xxx="version num" 这种依赖说明。

引入后,就相当于引入了一个符号 xxx,后面可以直接以这个 xxx 为根引用这个 crate 中的 item:

  1. extern crate xxx;
  2. use xxx::yyy::zzz;

引入的时候,可以通过 as 关键字重命名。

  1. extern crate xxx as foo;
  2. use foo::yyy::zzz;

第5章:Rust花絮

没想好写什么,暂时空白。

特别章:❌错误处理

Rust解决了空指针的问题,而且不用异常。

Rust的❌错误处理很有特色,我们看看例子:

1 简单一些的Option

  1. fn get(x: i32) -> Option<i32> {
  2. match x>0 {
  3. true => Some(1),
  4. false => None
  5. }
  6. }
  7. fn main() {
  8. let x : i32;
  9. x = get(1).unwrap();
  10. println!("x has the value {}", x);
  11. }

Option是一个Enum类型,有两个值Some和None,例子中用match分别处理。unwrap适合于演示代码,直接对None引发panic。

2 更复杂错误处理的情况用Result,看例子:

  1. use std::io;
  2. use std::io::Read;
  3. use std::fs::File;
  4. fn read_username_from_file() -> Result<String, io::Error> {
  5. let f = File::open("hello.txt");
  6. let mut f = match f {
  7. Ok(file) => file,
  8. Err(e) => return Err(e),
  9. };
  10. let mut s = String::new();
  11. match f.read_to_string(&mut s) {
  12. Ok(_) => Ok(s),
  13. Err(e) => Err(e),
  14. }
  15. }
  16. fn main() {
  17. let result : Result<String, io::Error>;
  18. result = read_username_from_file();
  19. println!("{:?}", result);
  20. }

Result也是Enum类型,两个值分别是Ok和Err,可以方便地传递错误信息。

3 简化错误传递的问号操作

  1. use std::io;
  2. use std::io::Read;
  3. use std::fs::File;
  4. fn read_username_from_file() -> Result<String, io::Error> {
  5. let mut f = File::open("hello.txt")?;
  6. let mut s = String::new();
  7. f.read_to_string(&mut s)?;
  8. Ok(s)
  9. }
  10. //You can link the call
  11. //File::open("hello.txt")?.read_to_string(&mut s)?;
  12. fn main() {
  13. println!("{:?}", read_username_from_file());
  14. }

?操作是用来简化错误的传递。

你可以尝试去掉末尾的❓,你会发现编译器准确地发现了错误,这大大提升了你对代码的信心,不需要花额外的功夫去写单元测试。

小结:

通过几个简单例子,大致学完了错误处理。

概括起来说,大致有两个方式:

  • Option

  • Result

Option,包括Some和None。适合于简单情况。

  1. enum Option<T> {
  2. Some(T),
  3. None,
  4. }

Result,包括Ok和Err。用于传递错误信息。

  1. enum Result<T, E> {
  2. Ok(T),
  3. Err(E),
  4. }
  1. 简化错误传递的?操作,可以选用。演示代码中常见到unwrap,会panic。还有一种expect("info"),也是panic,不过会带上info

好消息是消除了空指针问题,不利的是似乎有些复杂,需要时间来消化。

就这样,下一个话题。

第6章:并发

  1. extern crate rayon;
  2. use rayon::prelude::*;
  3. fn main() {
  4. let mut colors = [-20.0f32, 0.0, 20.0, 40.0,
  5. 80.0, 100.0, 150.0, 180.0, 200.0, 250.0, 300.0];
  6. println!("original: {:?}", &colors);
  7. colors.par_iter_mut().for_each(|color| {
  8. let c : f32 = if *color < 0.0 {
  9. 0.0
  10. } else if *color > 255.0 {
  11. 255.0
  12. } else {
  13. *color
  14. };
  15. *color = c / 255.0;
  16. });
  17. println!("transformed: {:?}", &colors);
  18. }

这个例子是通过库实现的并发,看上去比较简单。

关于并发我还缺少经验,所以就点到为止。留待以后再探讨。

第7章:宏

这里简单介绍下。

  1. macro_rules! create_function {
  2. ($func_name:ident) => (
  3. fn $func_name() {
  4. println!("function {:?} is called", stringify!($func_name))
  5. }
  6. )
  7. }
  8. fn main() {
  9. create_function!(foo);
  10. foo();
  11. }

第8章:测试

Rust 的测试特性按精细度划分,分为 3 个层次:

  1. 函数级;

  2. 模块级;

  3. 工程级;

另外,Rust 还支持对文档进行测试。

这里介绍简单的函数级测试。

  1. #[test]
  2. fn it_works() {
  3. assert!(2>1); // do test work
  4. }

非常简单,Rust 中,只需要在一个函数的上面,加上 #[test] 就标明这是一个测试用的函数。

有了这个属性之后,在使用 cargo build 编译时,就会忽略这些函数。使用 cargo test 可以运行这些函数。

Rust 提供了两个宏来执行测试断言:

  1. assert!(expr) 测试表达式是否为 true false
  2. assert_eq!(expr, expr) 测试两个表达式的结果是否相等

——实践小项目:Json处理

这个项目参考RustPrimer书,《Json处理》。介绍了caogo的用法。

通过小项目可以验证一下我们对Rust的理解是否正确。

结论

终于学习完了,看看你花了多少时间,入门了么?

希望学习愉快,等待下一个语言,下次再见!