0%

《Rust》Rust入门笔记

前言

Rust 是一门新兴编程语言,以执行效率堪比 C++ 著称。如果你的场景需要高效率低延时,Rust是你的最佳选择,C++ 的话,会让你的维护成本很大。

比较知名的 Rust 编写的应用有:

  1. 以太坊协议实现者 Parity。
  2. Facebook 的 Libra。
  3. 终端 alacritty。
  4. 数据库 Tikv。

下面是我自学过程中的笔记,都是相对其他语言的特点,做些笔记以便查阅,等到熟练开发rust之后,这些笔记就可以退役了。(不要过分依赖笔记,能记住最好)

trait

trait(特点) 类似于 go中的 interface,不同的是可以定义默认方法, 默认方法也可以被重写

trait可以继承。这样的话,实现FooBar也必须实现Foo

1
2
3
4
5
6
7
8
9
10
11
12
13
trait FooBar : Foo {
fn foobar(&self);
}

struct Baz {}

impl Foo for Baz {
fn foo(&self) { println!("foo"); }
}

impl FooBar for Baz {
fn foobar(&self) { println!("foobar"); }
}

Ownership

在rust中,每个资源只能拥有一个主人,资源所在的作用域消失,主人会自动释放资源

move:就是换主人。类似于golang的引用传递,但不太一样

1
2
3
let v = vec![1, 2, 3];  //创建一个vector,并绑定到一个变量。v就是vector的主人
let v2 = v; //把它赋给另一个变量。vector的主人换成了v2,v不再拥有这个资源
println!("v[0] is: {}", v[0]); // 这里就会报错。v不能访问它不拥有的资源

copy/clone: 既然主人不能新增只能更换,可以通过复制资源实现两个主人拥有一样的资源。类似于golang的值传递

Copy 会自动调用而 Clone 不会, clone必须开发者手动调用 a.clone。Clone trait可以被重写

  1. 常见的数字类型、bool类型、共享借用指针&,都是具有 Copy 属性的类型。
1
2
3
let aa: u64 = 1; // u64 实现了 Copy trait
let bb: u64 = aa; // 这里自动调用了aa.copy
println!("{}", aa); // aa还是拥有自己的资源
  1. Box、Vec、可写借用指针&mut 等类型都是不具备 Copy 属性的类型。

  2. 对于数组类型,如果它内部的元素类型是Copy,那么这个数组也是Copy类型。

  3. 对于tuple类型,如果它的每一个元素都是Copy类型,那么这个tuple会自动实现Copy trait。

  4. 对于struct和enum类型,不会自动实现Copy trait。而且只有当struct和enum内部每个元素都是Copy类型的时候,编译器才允许我们针对此类型实现Copy trait。

borrow:就是主人借别人资源,但别人不能修改这个资源。通过传递指针就可以实现borrow

1
2
3
4
5
6
7
fn foo(v: &Vec<i32>) {
v.push(5); // 尝试修改别人的资源。会报错
}

let v = vec![];

foo(&v); // v拥有的资源借给了 foo 函数中的v

A借给B资源,如果A修改了资源,则B就不能再用A的资源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#[derive(Debug)]
struct Solution {
A: String
}

impl Solution {

}

fn main () {
let mut a = Solution{
A: String::from("1"),
};
let b = &a; // 借用资源
a.A = String::from("123");
println!("{:?}", a);
println!("{:?}", b); // 这里会报错
}

可被修改的borrow:就是主人借别人资源,但别人可以修改这个资源。通过 &mut 可实现

1
2
3
4
5
6
let mut x = 5; // 首先 mut 表示主人可以变更资源
{ // 这个作用域是必须的。rust规定:同一个作用域内,不可以同时存在多个人对同一个资源有变更权限(类似读写锁)
let y = &mut x; // 可变资源以可变方式租借给y
*y += 1; // y更改这个资源
}
println!("{}", x);

Lifetime

rust规定:借用资源的债主的生命周期必须比主人的生命周期短

当编译器不清楚债主的生命周期时(当生命周期跨函数的时候),必须通过 ‘{自定义字符} 手动传递生命周期

另外,’static 是一个特殊的生命周期,它代表的是,这个程序从开始到结束的整个阶段,所以它比其它任何生命周期都长

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct T {
member: i32,
}

fn test<'a, 'b>(arg: &'a T)
-> &'b i32
where 'a:'b // 指定 'a >= 'b, 'a 比 'b 活得久
{
&arg.member // arg生命周期是 'a,那么这里函数返回值生命周期也是 'a,但强转成了 'b,返回值生命周期就是 'b
}

fn main() {
let t=T{member:12};
let x=test(&t); // t的生命周期传递给了 arg,也就是 'a 等于t的生命周期。x的生命周期就是 test 函数的返回值的生命周期 'b,因为 'a 生命周期长于 'b 生命周期,所以t生命周期长于x。故这里不会报错
println!("{:?}", x);
}

编译器允许省略 ‘a 这些生命周期声明。上面简化后就是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct T {
member: i32,
}

fn test(arg: &T)
-> &i32
{
&arg.member
}

fn main() {
let t=T{member:12};
let x=test(&t);
println!("{:?}", x);
}

rust-toolchain文件

rust-toolchain文件放在项目根目录,可以指示本项目使用什么rust版本的编译器

比如:
内容如果是 nightly-2019-11-05,表示使用 nightly-2019-11-05 版本的编译器

格式化输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
use std::fmt::Display;
use std::fmt::Formatter;
use std::fmt::Result;
#[derive(Debug)]
struct T {
member: i32,
}
impl Display for T {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
// `write!` 和 `format!` 类似,但它会将格式化后的字符串写入
// 一个缓冲区(即第一个参数f)中。
write!(f, "member: {}", self.member)
}
}
fn main() {
let t=T{member:12};
let a = 21u32;
println!("{:#?}", a); // 基本类型都实现了Debug和Dispaly
println!("{:#}", a);
println!("{:#?}", t); // 带?是Debug输出,需要输出对象实现Debug trait。#[derive(Debug)]注解可以让编译器自动加上Debug实现的代码
println!("{:#}", t); // 不带?是Display输出,需要输出对象实现Display trait。需要自行实现
println!("{:#}", t.to_string()); // 实现了Display就会自动具有to_string方法可用
}

From、Into trait (TryFrom、TryInto 类似)

TryFrom、TryInto 与上面类似,只不过返回值为Result,可能返回错误

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#[derive(Debug)]
struct T {
member: i32,
}
impl From<i32> for T {
fn from(num: i32) -> Self {
T{member: num}
}
}
fn main() {
let a: i32 = 21;
println!("{:#?}", T::from(a));
let num: T = a.into(); // 因为T实现了From,就自动有了into方法可用,编译器可以推断
println!("{:#?}", num);
// println!("{:#?}", a.into()); // 这样是不行的,必须要T类型声明,不然编译器无法推断
}

函数返回值

与其他语言不同,rust返回值不使用return,而是最后一行不以分号结尾表示返回值

不过也可以使用 return 关键字

FromStr trait

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
use std::str::FromStr;
use std::num::ParseIntError;
#[derive(Debug)]
struct T {
member: String,
}
impl FromStr for T {
type Err = ParseIntError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(T{member: String::from(s)})
}
}
fn main() {
let t: T = "aa".parse().unwrap(); // 实现了FromStr trait,字符串就会有parse函数可用。必须声明结果的类型T,否则编译器无法推断parse是谁提供的
println!("{:#?}", t);
}

iter、into_iter、iter_mut

三者都是转化为迭代器
iter
是借用集合中每个元素,这样集合本身不会被改变,循环之后仍可以使用

into_iter
会消耗集合。在每次迭代中,集合中的数据本身会被提供。一旦集合被消 耗了,之后就无法再使用了,因为它已经在循环中被 “移除”(move)了

iter_mut
可变地(mutably)借用集合中的每个元素,从而允许集合被就地修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

fn main() {
let mut names = vec!["Bob", "Frank", "Ferris"];
for name in names.iter_mut() {
*name = match name {
&mut "Ferris" => "There is a rustacean among us!",
_ => "Hello",
};
}
println!("names: {:?}", names);

let names1 = vec!["Bob", "Frank", "Ferris"];
for name in names1.into_iter() {
match name {
"Ferris" => "There is a rustacean among us!",
_ => "Hello",
};
}
// println!("names: {:?}", names1); // 这里会报错了,names1已经不能再用
}

if let (while let 类似)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
enum Foo {
Qux(u32)
}
fn main() {
let msg = Some("howdy");
if let Some(m) = &msg { // 类型判断并赋值
println!("{}", *m);
}
if let Some(m) = msg { // 类型判断并赋值
println!("{}", m);
}

let msg1 = "howdy";
if let "231" = msg1 { // 判断。与if效果一致
println!("{}", "32");
} else {
println!("{}", "11");
}

let c = Foo::Qux(100);
if let Foo::Qux(value) = c { // 类型判断并赋值
println!("c is {}", value);
}
}

mod 可见性

默认情况下,模块中的项拥有私有的可见性。但可以通过pub进行暴露

pub 公开。任何地方可以访问

pub(crate) 项可以在同一个 crate 中的任何地方访问

pub(in mod_name) 项只能在指定的模块中访问

pub(self) 只在当前模块中可见

pub(super) 只在父模块中可见

super、self关键字

super表示父模块,self表示本模块

crate

crate 类似于C#中的动态链接库

引用 crate 使用 extern crate crate_name

条件编译

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#[cfg(target_os = "linux")] // 这个函数仅当目标系统是 Linux 的时候才会编译
fn are_you_on_linux() {
println!("You are running linux!")
}
#[cfg(not(target_os = "linux"))] // 而这个函数仅当目标系统不是 Linux 时才会编译
fn are_you_on_linux() {
println!("You are *not* running linux!")
}
#[cfg(some_condition)] // 自定义条件。需要传入进来 rustc --cfg some_condition custom.rs
fn are_you_on_linux() {
println!("You are *not* running linux!")
}
fn main() {
are_you_on_linux();

println!("Are you sure?");
if cfg!(target_os = "linux") {
println!("Yes. It's definitely linux!");
} else {
println!("Yes. It's definitely *not* linux!");
}
}

运算符重载

a + b 中的 + 运算符会调用 add 方法(也就是 a.add(b))。这个 add 方 法是 Add trait 的一部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
use std::ops;

struct Foo;
struct Bar;

#[derive(Debug)]
struct FooBar;

#[derive(Debug)]
struct BarFoo;

// `std::ops::Add` trait 用来指明 `+` 的功能,这里我们实现 `Add<Bar>`,它是用于
// 把对象和 `Bar` 类型的右操作数(RHS)加起来的 `trait`。
// 下面的代码块实现了 `Foo + Bar = FooBar` 这样的运算。
impl ops::Add<Bar> for Foo {
type Output = FooBar;

fn add(self, _rhs: Bar) -> FooBar {
println!("> Foo.add(Bar) was called");

FooBar
}
}

// 通过颠倒类型,我们实现了不服从交换律的加法。
// 这里我们实现 `Add<Foo>`,它是用于把对象和 `Foo` 类型的右操作数加起来的 trait。
// 下面的代码块实现了 `Bar + Foo = BarFoo` 这样的运算。
impl ops::Add<Foo> for Bar {
type Output = BarFoo;

fn add(self, _rhs: Foo) -> BarFoo {
println!("> Bar.add(Foo) was called");

BarFoo
}
}

fn main() {
println!("Foo + Bar = {:?}", Foo + Bar);
println!("Bar + Foo = {:?}", Bar + Foo);
}

Drop trait

Drop trait 只有一个方法:drop,当对象离开作用域时会自动调用该 方法。Drop trait 的主要作用是释放实现者的实例拥有的资源。

Box,Vec,String,File,以及 Process 是一些实现了 Drop trait 来释放 资源的类型。Drop trait 也可以为任何自定义数据类型手动实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
struct Droppable {
name: &'static str,
}

// 这个简单的 `drop` 实现添加了打印到控制台的功能。
impl Drop for Droppable {
fn drop(&mut self) {
println!("> Dropping {}", self.name);
}
}

fn main() {
let _a = Droppable { name: "a" };

// 代码块 A
{
let _b = Droppable { name: "b" };

// 代码块 B
{
let _c = Droppable { name: "c" };
let _d = Droppable { name: "d" };

println!("Exiting block B");
}
println!("Just exited block B");

println!("Exiting block A");
}
println!("Just exited block A");

// 变量可以手动使用 `drop` 函数来销毁。
drop(_a);
// 试一试 ^ 将此行注释掉。

println!("end of the main function");

// `_a` *不会*在这里再次销毁,因为它已经被(手动)销毁。
}

Iterator trait

Iterator trait 用来对集合(collection)类型(比如数组)实现迭代器。

这个 trait 只需定义一个返回 next(下一个)元素的方法,这可手动在 impl 代码块 中定义,或者自动定义(比如在数组或区间中)。

为方便起见,for 结构会使用 .into_iterator() 方法将一些集合类型 转换为迭代器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
struct Fibonacci {
curr: u32,
next: u32,
}

// 为 `Fibonacci`(斐波那契)实现 `Iterator`。
// `Iterator` trait 只需定义一个能返回 `next`(下一个)元素的方法。
impl Iterator for Fibonacci {
type Item = u32;

// 我们在这里使用 `.curr` 和 `.next` 来定义数列(sequence)。
// 返回类型为 `Option<T>`:
// * 当 `Iterator` 结束时,返回 `None`。
// * 其他情况,返回被 `Some` 包裹(wrap)的下一个值。
fn next(&mut self) -> Option<u32> {
let new_next = self.curr + self.next;

self.curr = self.next;
self.next = new_next;

// 既然斐波那契数列不存在终点,那么 `Iterator` 将不可能
// 返回 `None`,而总是返回 `Some`。
Some(self.curr)
}
}

// 返回一个斐波那契数列生成器
fn fibonacci() -> Fibonacci {
Fibonacci { curr: 1, next: 1 }
}

fn main() {
// `0..3` 是一个 `Iterator`,会产生:0、1 和 2。
let mut sequence = 0..3;

println!("Four consecutive `next` calls on 0..3");
println!("> {:?}", sequence.next());
println!("> {:?}", sequence.next());
println!("> {:?}", sequence.next());
println!("> {:?}", sequence.next());

// `for` 遍历 `Iterator` 直到返回 `None`,
// 并且每个 `Some` 值都被解包(unwrap),然后绑定给一个变量(这里是 `i`)。 println!("Iterate through 0..3 using `for`");
for i in 0..3 {
println!("> {}", i);
}

// `take(n)` 方法提取 `Iterator` 的前 `n` 项。
println!("The first four terms of the Fibonacci sequence are: ");
for i in fibonacci().take(4) {
println!("> {}", i);
}

// `skip(n)` 方法移除前 `n` 项,从而缩短了 `Iterator` 。
println!("The next four terms of the Fibonacci sequence are: ");
for i in fibonacci().skip(4).take(4) {
println!("> {}", i);
}

let array = [1u32, 3, 3, 7];

// `iter` 方法对数组/slice 产生一个 `Iterator`。
println!("Iterate the following array {:?}", &array);
for i in array.iter() {
println!("> {}", i);
}
}

macro_rules! 创建宏

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
macro_rules! create_function {
// 此宏接受一个 `ident` 指示符表示的参数,并创建一个名为 `$func_name` 的函数。
// `ident` 指示符用于变量名或函数名
($func_name:ident) => (
fn $func_name() {
// `stringify!` 宏把 `ident` 转换成字符串。
println!("You called {:?}()",
stringify!($func_name))
}
)
}

// 借助上述宏来创建名为 `foo` 和 `bar` 的函数。
create_function!(foo);
create_function!(bar);

macro_rules! print_result {
// 此宏接受一个 `expr` 类型的表达式,并将它作为字符串,连同其结果一起
// 打印出来。
// `expr` 指示符表示表达式。
($expression:expr) => (
// `stringify!` 把表达式*原样*转换成一个字符串。
println!("{:?} = {:?}",
stringify!($expression),
$expression)
)
}

macro_rules! calculate {
(eval $e:expr) => {{
{
let val: usize = $e; // 强制类型为整型
println!("{} = {}", stringify!{$e}, val);
}
}};
}

fn main() {
foo();
bar();

print_result!(1u32 + 1);

// 回想一下,代码块也是表达式!
print_result!({
let x = 1u32;

x * x + 2 * x - 1
});

calculate! {
eval 1 + 2 // 看到了吧,`eval` 可并不是 Rust 的关键字!
}
}

动态数组 vector

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
fn main() {
// 迭代器可以被收集到 vector 中
let collected_iterator: Vec<i32> = (0..10).collect();
println!("Collected (0..10) into: {:?}", collected_iterator);

// `vec!` 宏可用来初始化一个 vector
let mut xs = vec![1i32, 2, 3];
println!("Initial vector: {:?}", xs);

// 在 vector 的尾部插入一个新的元素
println!("Push 4 into the vector");
xs.push(4);
println!("Vector: {:?}", xs);

// 报错!不可变 vector 不可增长
collected_iterator.push(0);
// 改正 ^ 将此行注释掉

// `len` 方法获得一个 vector 的当前大小
println!("Vector size: {}", xs.len());

// 下标使用中括号表示(从 0 开始)
println!("Second element: {}", xs[1]);

// `pop` 移除 vector 的最后一个元素并将它返回
println!("Pop last element: {:?}", xs.pop());

// 超出下标范围将抛出一个 panic
println!("Fourth element: {}", xs[3]);
// 改正 ^ 注释掉此行

// 迭代一个 `Vector` 很容易
println!("Contents of xs:");
for x in xs.iter() {
println!("> {}", x);
}

// 可以在迭代 `Vector` 的同时,使用独立变量(`i`)来记录迭代次数
for (i, x) in xs.iter().enumerate() {
println!("In position {} we have value {}", i, x);
}

// 多亏了 `iter_mut`,可变的 `Vector` 在迭代的同时,其中每个值都能被修改
for x in xs.iter_mut() {
*x *= 3;
}
println!("Updated vector: {:?}", xs);
}

散列表 HashMap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
use std::collections::HashMap;

fn call(number: &str) -> &str {
match number {
"798-1364" => "We're sorry, the call cannot be completed as dialed.
Please hang up and try again.",
"645-7689" => "Hello, this is Mr. Awesome's Pizza. My name is Fred.
What can I get for you today?",
_ => "Hi! Who is this again?"
}
}

fn main() {
let mut contacts = HashMap::new();

contacts.insert("Daniel", "798-1364");
contacts.insert("Ashley", "645-7689");
contacts.insert("Katie", "435-8291");
contacts.insert("Robert", "956-1745");

// 接受一个引用并返回 Option<&V>
match contacts.get(&"Daniel") {
Some(&number) => println!("Calling Daniel: {}", call(number)),
_ => println!("Don't have Daniel's number."),
}

// 如果被插入的值为新内容,那么 `HashMap::insert()` 返回 `None`,
// 否则返回 `Some(value)`
contacts.insert("Daniel", "164-6743");

match contacts.get(&"Ashley") {
Some(&number) => println!("Calling Ashley: {}", call(number)),
_ => println!("Don't have Ashley's number."),
}

contacts.remove(&("Ashley"));

// `HashMap::iter()` 返回一个迭代器,该迭代器以任意顺序举出
// (&'a key, &'a value) 对。
for (contact, &number) in contacts.iter() {
println!("Calling {}: {}", contact, call(number));
}
}

panic、Option、Result

panic可以主动抛出异常。类似于throw

Option用于一个函数返回内容或者返回空内容,js里面返回空内容时一般使用空字符串或者null,rust中可以使用Option

Result用于一个函数返回内容或者返回错误,类似于golang中的返回

move关键字

move关键字后面接闭包,强制闭包夺取所有外部引用变量的所有权(如果外部变量实现了Copy,则是所有权拷贝)

多线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
use std::thread;
use std::sync::mpsc::{Sender, Receiver};
use std::sync::mpsc;

static NTHREADS: i32 = 10;

// 这是主(`main`)线程
fn main() {
// 提供一个 vector 来存放所创建的子线程(children)。
let mut children = vec![];

for i in 0..NTHREADS {
// 启动(spin up)另一个线程
children.push(thread::spawn(move || {
println!("this is thread number {}", i);
"123"
}));
}

for child in children {
let result = child.join().unwrap(); // join可以取出线程的返回值,其实join之前线程已经执行了
println!("{}", result)
}


// 通道有两个端点:`Sender<T>` 和 `Receiver<T>`,其中 `T` 是要发送
// 的消息的类型(类型标注是可选的)
let (tx, rx): (Sender<i32>, Receiver<i32>) = mpsc::channel();

for id in 0..NTHREADS {
// sender 端可被复制
let thread_tx = tx.clone();

// 每个线程都将通过通道来发送它的 id
thread::spawn(move || {
// 被创建的线程取得 `thread_tx` 的所有权
// 每个线程都把消息放在通道的消息队列中
thread_tx.send(id).unwrap();

// 发送是一个非阻塞(non-blocking)操作,线程将在发送完消息后
// 会立即继续进行
println!("thread {} finished", id);
});
}

// 所有消息都在此处被收集
let mut ids = Vec::with_capacity(NTHREADS as usize);
for _ in 0..NTHREADS {
// `recv` 方法从通道中拿到一个消息
// 若无可用消息的话,`recv` 将阻止当前线程
ids.push(rx.recv());
}

// 显示消息被发送的次序
println!("{:?}", ids);
}

子进程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
use std::process::Command;

fn main() {
let output = Command::new("node")
.arg("-v")
.output().unwrap_or_else(|e| {
panic!("failed to execute process: {}", e)
});

if output.status.success() {
let s = String::from_utf8_lossy(&output.stdout);

print!("rustc succeeded and stdout was:\n{}", s);
} else {
let s = String::from_utf8_lossy(&output.stderr);

print!("rustc failed and stderr was:\n{}", s);
}
}



微信关注我,及时接收最新技术文章