目录

Learn Rust

Install

https://www.rust-lang.org/tools/install

Common Programming Concepts

Variables And Mutability

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
fn main() {
    let x = 5;
    x = 6; // error x是不可变的
    
	const MAX_POINTS: u32 = 100_000; // 常量, 和不可变的变量不同,常量只能由常量表达式初始化
    // 数字里的下划线只是提高可读性
    
    let mut x = 5; // 同时 shadow 上面的 x, 这是一个新的变量,类型也可以不同
    x = 6; // 正确
}

数据类型 Data Type

Rust has four primary scalar types: integers, floating-point numbers, Booleans, and characters

Integer Types

Integer Types in Rust:

Length Signed Unsigned
8-bit i8 u8
16-bit i16 u16
32-bit i32 u32
64-bit i64 u64
128-bit i128 u128
arch isize usize

Integer Literals in Rust:

Number literals Example
Decimal 98_222
Hex 0xff
Octal 0o77
Binary 0b1111_0000
Byte (u8 only) b'A'

Floating-Point Types

Rust’s floating-point types are f32 and f64, which are 32 bits and 64 bits in size, respectively.

IEEE-754 standard:

/语言/f32.png

/语言/f64.png

The Boolean Type

1
2
3
4
5
fn main() {
    let t = true;

    let f: bool = false; // with explicit type annotation
}

Character Type

1
2
3
4
5
fn main() {
    let c = 'z';
    let z = 'ℤ';
    let heart_eyed_cat = '😻';
}

Rust’s char type is four bytes in size and represents a Unicode Scalar Value, which means it can represent a lot more than just ASCII.

Compound Types

组合类型,Compound types can group multiple values into one type. Rust has two primitive compound types: tuples and arrays.

The Tuple Type

Tuples have a fixed length: once declared, they cannot grow or shrink in size.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
fn main() {
    let tup: (i32, f64, u8) = (500, 6.4, 1);
    
    let tup2 = (500, 6.4, 1);
    let (x, y, z) = tup2;
    
    let x: (i32, f64, u8) = (500, 6.4, 1);
    let five_hundred = x.0;
    let six_point_four = x.1;
    let one = x.2;
}

The Array Type

arrays in Rust have a fixed length

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
fn main() {
    let a = [1, 2, 3, 4, 5];
    
    let a: [i32; 5] = [1, 2, 3, 4, 5]; // 指定类型和数量
    
    let a = [3; 5]; // 等同于 let a = [3, 3, 3, 3, 3];
    
    let first = a[0];
    let second = a[1];
}

函数 Function

1
2
3
4
5
6
7
8
9
fn main() {
    println!("Hello, world!");

    another_function();
}

fn another_function() {
    println!("Another function.");
}

Function Parameters

1
2
3
4
5
6
7
fn main() {
    another_function(5);
}

fn another_function(x: i32) {
    println!("The value of x is: {}", x);
}

Statement & Expression

语句(声明)和表达式的区别: 语句没有返回值,表达式则有 (这只是rust的规则

1
2
3
4
5
6
7
8
9
let x = 6; // 语句
let y = (let x); // 错误

let x = 5+6; // 5+6是表达式 ,表达式可以是声明的一部分
let y = {	// 使用 {} 括起来的块也是表达式, 最后一条不已分号结尾的是表达式返回值
    let x = 3;
    x + 1
};
// 

Return Value

1
2
3
4
5
6
7
fn five() -> i32 { // 最后一条不加分号的表达式,当作返回值
    5
}

fn five() -> i32 { // 也可以显示调用return,在任意位置返回
    return 5;
}

注释 Comments

普通注释

1
// comment

控制流 Control Flow

分支

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
if number % 4 == 0 {
    println!("number is divisible by 4");
} else if number % 3 == 0 {
    println!("number is divisible by 3");
} else if number % 2 == 0 {
    println!("number is divisible by 2");
} else {
    println!("number is not divisible by 4, 3, or 2");
}

let number = if condition { 5 } else { 6 }; // 不以分号结尾的,当作表达式返回值

循环

 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
fn main() {
    loop {
        println!("again!");
    }
}

// 带返回值的 loop
fn main() {
    let mut counter = 0;

    let result = loop {
        counter += 1;

        if counter == 10 {
            break counter * 2;
        }
    };

    println!("The result is {}", result); // 20
}

// 条件循环
while number != 0 {
    println!("{}!", number);

    number -= 1;
}

// for 循环
fn main() {
    let a = [10, 20, 30, 40, 50];

    for element in a.iter() {
        println!("the value is: {}", element);
    }
}
fn main() {
    for number in (1..4).rev() {
        println!("{}!", number); // 3! 2! 1!
    }
    println!("LIFTOFF!!!");
}

所有权 Ownership

rust中最重要的也是独有的概念,内存是通过带有一组规则的所有权系统管理的,编译器在编译时检查这些规则,不会影响程序的执行效率

所有权要解决的所有问题都是:跟踪代码的哪些部分正在使用堆上的哪些数据,最大程度地减少堆上的重复数据并清理堆上的未使用数据,以免耗尽空间。 管理堆数据是所有权存在的原因。

规则

  • Each value in Rust has a variable that’s called its owner.
  • Rust中每个值都有一个变量,称为它的所有者
  • There can only be one owner at a time.
  • 同一时刻只能有一个所有者
  • When the owner goes out of scope, the value will be dropped.
  • 当所有者超出作用域,该值会被删除

String type

基本数据类型都在栈上,所以用String类型演示,也可以是其他复杂类型complex data types

string literal 字符串字面值, 被硬编码在代码中,不能更改

1
let s = "hello";

String,这种类型分配在堆上,因此能够存储编译时未知的大量文本。可以通过字符串字面值来创建

1
2
let s = String::from("hello"); // :: namespace
let mut s = String::from("hello");

GC语言自动管理内存,C++有allocfree,而rust,一旦拥有内存的变量超出范围,就会自动归还内存。

1
2
3
4
{
    let s = String::from("hello"); // s is valid
}
// s 被释放

When a variable goes out of scope, Rust calls a special function for us. This function is called drop, and it’s where the author of String can put the code to return the memory. Rust calls drop automatically at the closing curly bracket.

变量与数据交互的方式:移动

1
2
let s1 = String::from("hello");
let s2 = s1;

如果是c++中,可能是如下图的浅拷贝

/语言/trpl04-02.svg

rust如果用浅拷贝,那么前面说了会自动调用drop,存在双重释放的问题

Rust认为s1不再有效,因此,当s1超出作用域时,它不需要释放任何内存。

1
2
3
4
let s1 = String::from("hello");
let s2 = s1;

println!("{}, world!", s1); // error 

区别浅拷贝,这种方式叫做移动 move 。或者叫转让更合适?

we would say that s1 was moved into s2.

Rust永远不会自动创建数据的深层副本。因此,就运行时性能而言,可以认为任何自动复制都是廉价的。

克隆

如果确实希望深入复制字符串的堆数据,而不仅仅是堆栈数据,可以使用一个称为clone的通用方法。

1
2
3
4
let s1 = String::from("hello");
let s2 = s1.clone();

println!("s1 = {}, s2 = {}", s1, s2); // ok

拷贝

1
2
let x = 5;
let y = x; // 这是拷贝,内置类型,大小固定,栈上分配,传拷贝

编译时具有已知大小的整数之类的类型完全存储在栈中,因此可以快速创建实际值的副本。 这意味着在创建变量y之后我们没有理由要阻止x生效。 换句话说,这里的深层复制和浅层复制没有什么区别,因此调用克隆与通常的浅层复制没有什么不同,可以省去它。

Rust has a special annotation called the Copy trait , 可以指示编译器将类型放在栈上,像一个整数类型一样

如果类型或它的任何一部分实现了Drop特性,Rust将不允许我们使用Copy特性来注释该类型。

以下是拷贝类型:

  • All the integer types, such as u32.
  • The Boolean type, bool, with values true and false.
  • All the floating point types, such as f64.
  • The character type, char.
  • Tuples, if they only contain types that are also Copy. For example, (i32, i32) is Copy, but (i32, String) is not.
  • 数组也是,内部元素必须是 copy 类型

所有权与函数

将变量传给函数,和赋值一样会发生拷贝或移动

1
2
3
4
5
6
7
let s = String::from("hello");  
takes_ownership(s);
// s 这里无效了

let x = 5;
makes_copy(x);
// x 仍然有效

返回值同样可以转让所有权

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
fn main() {
    let s1 = gives_ownership(); 

    let s2 = String::from("hello"); 

    let s3 = takes_and_gives_back(s2); 
    
    // s2 无效了
}

fn gives_ownership() -> String { 
    let some_string = String::from("hello"); 
    some_string                              
}

fn takes_and_gives_back(a_string: String) -> String { 
    a_string 
}

引用和借用 References and Borrowing

有时候我们不希望发生所有权转移,为了计算字符串长度,并继续使用字符串,下面的写法太不优雅

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
fn main() {
    let s1 = String::from("hello");

    let (s2, len) = calculate_length(s1);

    println!("The length of '{}' is {}.", s2, len);
}

fn calculate_length(s: String) -> (String, usize) {
    let length = s.len(); // len() returns the length of a String
    (s, length)
}

使用引用的写法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
fn main() {
    let s1 = String::from("hello");

    let len = calculate_length(&s1);

    println!("The length of '{}' is {}.", s1, len);
}

fn calculate_length(s: &String) -> usize {
    s.len()
}// Here, s goes out of scope. But because it does not have ownership of what
  // it refers to, nothing happens.

引用和指针很像(我怀疑这tm就是指针

/语言/trpl04-05.svg

&s1创建了一个指向s1的引用,但是并不拥有它,所以引用离开作用域时,不会影响s1

Because it does not own it, the value it points to will not be dropped when the reference goes out of scope.

同样,函数的签名使用&表示参数s的类型是一个引用。

如果在calculate_length试图修改传入的内容是无效的

1
2
3
fn change(some_string: &String) {
    some_string.push_str(", world"); // error
}

Just as variables are immutable by default, so are references. We’re not allowed to modify something we have a reference to.

可变引用可以解决这个问题

1
2
3
4
5
6
7
8
9
fn main() {
    let mut s = String::from("hello");

    change(&mut s);
}

fn change(some_string: &mut String) { // &mut 可变引用
    some_string.push_str(", world");
}

对于特定范围内的特定数据片段,只能有一个可变引用。下面的代码是错误的

1
2
3
4
5
6
let mut s = String::from("hello");

let r1 = &mut s;
let r2 = &mut s; // error

println!("{}, {}", r1, r2);

同时只能存在一个,看下面代码,是可以的

1
2
3
4
5
6
7
let mut s = String::from("hello");

{
    let r1 = &mut s;
} // r1 goes out of scope here, so we can make a new reference with no problems.

let r2 = &mut s;

对于组合可变引用和不可变引用,也有限制,(和读写锁很像)

1
2
3
4
5
6
7
let mut s = String::from("hello");

let r1 = &s; // no problem
let r2 = &s; // no problem
let r3 = &mut s; // BIG PROBLEM

println!("{}, {}, and {}", r1, r2, r3);

请注意,引用的范围从引入它的地方开始,一直持续到最后一次使用该引用

例如,此代码将被编译,因为不可变引用的最后一次使用发生在引入可变引用之前:

1
2
3
4
5
6
7
8
9
let mut s = String::from("hello");

let r1 = &s; // no problem
let r2 = &s; // no problem
println!("{} and {}", r1, r2);
// r1 and r2 are no longer used after this point

let r3 = &mut s; // no problem
println!("{}", r3);

编译器保证引用永远不会有悬空引用(类比c++就是悬空指针)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
fn main() {
    let reference_to_nothing = dangle();
}

fn dangle() -> &String {
    let s = String::from("hello");

    &s
} // 这里返回了引用,然后s销毁了,所以引用悬空了,编译器不会让这样的情况发生
// 这里应该直接返回字符串

总结

  • At any given time, you can have either one mutable reference or any number of immutable references.
  • References must always be valid.

切片 slices

a different kind of reference: slices.

没有所有权的另一种数据类型是切片。 切片可以引用集合中连续的元素序列,而不是整个集合。

编写一个函数,该函数需要一个字符串并返回在该字符串中找到的第一个单词。 如果函数在字符串中找不到空格,则整个字符串必须是一个单词,因此应返回整个字符串。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
fn first_word(s: &String) -> usize {
    let bytes = s.as_bytes();

    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return i;
        }
    }

    s.len()
}

Because the enumerate method returns a tuple, we can use patterns to destructure that tuple, just like everywhere else in Rust.

Because we get a reference to the element from .iter().enumerate(), we use & in the pattern.

这个程序返回一个数字表示第一个单词的结尾,但如果字符串变了,这个数字就没有意义了

解决这个问题的方法是 字符串切片

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
let s = String::from("hello world");

let hello = &s[0..5];
let world = &s[6..11];

// let slice = &s[0..2];
// let slice = &s[..2]; 等效
// let slice = &s[3..len];
// let slice = &s[3..]; 等效
// let slice = &s[0..len];
// let slice = &s[..]; 等效

这类似于引用整个String,但带有额外的[0..5]。 它不是对整个String的引用,而是对String的一部分的引用。

/语言/trpl04-06.svg

字符串切片范围索引必须出现在有效的UTF-8字符边界处。 如果尝试在多字节字符的中间创建字符串切片,则程序将退出并出现错误。

不过String底层是byte数组,为啥不能切到中间? 疑惑

表示字符串片的类型写为 &str

函数优雅的实现方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
fn first_word(s: &String) -> &str {
    let bytes = s.as_bytes();

    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &s[0..i];
        }
    }

    &s[..]
}

下面的代码是错误的,因为

1
2
3
4
5
6
7
8
9
fn main() {
    let mut s = String::from("hello world");

    let word = first_word(&s);

    s.clear(); // error!

    println!("the first word is: {}", word);
}

如果某事物具有不变的引用,那么就不能采用可变的引用。 由于clear需要截断String,因此需要获取可变的引用。 Rust不允许这样做,并且编译失败。

if we have an immutable reference to something, we cannot also take a mutable reference. Because clear needs to truncate the String, it needs to get a mutable reference. Rust disallows this, and compilation fails.

字符串字面值是切片

1
let s = "Hello, world!";

The type of s here is &str: it’s a slice pointing to that specific point of the binary. This is also why string literals are immutable; &str is an immutable reference.

有经验的人会使用第二种写法,既可以接收String又可以传&str

1
2
3
4
fn first_word(s: &String) -> &str { 
}
fn first_word(s: &str) -> &str {
}

如果我们有一个字符串切片,我们可以直接传递它。 如果我们有一个String,我们可以传递整个String的一部分。 定义函数以采用字符串切片而不是对String的引用使我们的API更通用和有用,而不会丢失任何功能:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
fn main() {
    let my_string = String::from("hello world");

    let word = first_word(&my_string[..]);

    let my_string_literal = "hello world";
	// 可以在一个切片上,继续创建切片
    let word = first_word(&my_string_literal[..]);

    let word = first_word(my_string_literal);
}
其他切片

字符串切片是特定于字符串的。 还有一种更通用的切片类型。

引用数组的一部分

1
2
3
let a = [1, 2, 3, 4, 5];

let slice = &a[1..3]; // &[i32]

You’ll use this kind of slice for all sorts of other collections.

您将对其他各种集合使用这种切片

结构体 Structs

声明与创建

定义一个结构体

1
2
3
4
5
6
struct User {
    username: String,
    email: String,
    sign_in_count: u64,
    active: bool,
}

创建一个实例

1
2
3
4
5
6
7
let mut user1 = User {
    email: String::from("someone@example.com"),
    username: String::from("someusername123"),
    active: true,
    sign_in_count: 1,
};
user1.email = String::from("anotheremail@example.com");

当变量和字段重名时,可以简写

1
2
3
4
5
6
7
8
fn build_user(email: String, username: String) -> User {
    User {
        email,
        username,
        active: true,
        sign_in_count: 1,
    }
}

用另一个实例初始化当前实例

1
2
3
4
5
let user2 = User {
    email: String::from("another@example.com"),
    username: String::from("anotherusername567"),
    ..user1
};

使用没有命名字段的元组结构创建不同的类型

1
2
3
4
5
struct Color(i32, i32, i32);
struct Point(i32, i32, i32); // 他们时不同类型

let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);

还可以创建一个没有任何字段的空类

Derived Traits

有时候打印结构体的信息很有用,但直接像下面这样使用println!宏是不行的

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
struct Rectangle {
    width: u32,
    height: u32,
}

fn main() {
    let rect1 = Rectangle {
        width: 30,
        height: 50,
    };

    println!("rect1 is {}", rect1); 
    // err `Rectangle` doesn't implement `std::fmt::Display`
    // help the trait `std::fmt::Display` is not implemented for `Rectangle`
    // note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
}

基本类型默认情况下实现了Display,但是自定义结构体并没有

编译器提示我们可以用std::fmt::Debug的方式打印,就是{:?},但是直接替换也是不行的,编译器提示结构体没有实现std::fmt::Debug,应该加一个注解

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

fn main() {
    let rect1 = Rectangle {
        width: 30,
        height: 50,
    };

    println!("rect1 is {:?}", rect1); // rect1 is Rectangle { width: 30, height: 50 }
    // 如果使用 {:#?}
    //rect1 is Rectangle {
    //	width: 30,
    //	height: 50,
	//}
}

Rust has provided a number of traits for us to use with the derive annotation that can add useful behavior to our custom types.

方法

声明一个方法

 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
#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    fn area(&self) -> u32 { // 不可变借用自己
        self.width * self.height
    }
    
    fn can_hold(&self, other: &Rectangle) -> bool {
        self.width > other.width && self.height > other.height
    }
}

fn main() {
    let rect1 = Rectangle {
        width: 30,
        height: 50,
    };

    println!(
        "The area of the rectangle is {} square pixels.",
        rect1.area()
    );
}

To define the function within the context of Rectangle, we start an impl (implementation) block.

rust没有c++中的->操作符,解除引用时自动完成的

Rust has a feature called automatic referencing and dereferencing.

Here’s how it works: when you call a method with object.something(), Rust automatically adds in &, &mut, or * so object matches the signature of the method. In other words, the following are the same:

1
2
p1.distance(&p2);	// 等效
(&p1).distance(&p2);

Associated Functions 关联函数,类似c++的静态成员函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
impl Rectangle {
    fn square(size: u32) -> Rectangle {
        Rectangle {
            width: size,
            height: size,
        }
    }
}

// 调用时使用 ::
let sq = Rectangle::square(3);

the :: syntax is used for both associated functions and namespaces created by modules.

::用在关联函数和命名空间

每个结构都可以具有多个impl

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }
}

impl Rectangle {
    fn can_hold(&self, other: &Rectangle) -> bool {
        self.width > other.width && self.height > other.height
    }
}

这里没有理由将这些方法分为多个impl块,但这是有效的语法。一种情况下,其中多个impl块很有用,泛型类型和特征(traits)。

枚举 Enums