Rust入门笔记

HelloWorld

1
2
3
fn main() {
    println!("Hello, world!");
}

! 表示这是一个宏(macro),而非一个函数(function)

1
2
rustc hello.rs
./hello

Installation

1
2
3
brew install rustup-init
export RUSTUP_DIST_SERVER=https://mirrors.tuna.tsinghua.edu.cn/rustup
rustup-init
1
2
rustc --version
cargo --version

编辑 $HOME/.cargo/config

1
2
3
4
5
[source.crates-io]
replace-with = 'tuna'

[source.tuna]
registry = "https://mirrors.tuna.tsinghua.edu.cn/git/crates.io-index.git"

Cargo

包管理类似pip npm 但还提供一些工作流 创建新项目:cargo new

编译:cargo build

运行:cargo run

更新项目依赖:cargo update

执行测试:cargo test

生成文档:cargo doc

静态检查:cargo check

1
2
3
4
5
$ tree
.
├── Cargo.toml
└── src
    └── main.rs

Cargo.toml 是工程的描述文件,包含 Cargo 所需的所有元信息。

1
2
3
4
5
6
7
8
9
[package]
name = "helloworld"
version = "0.1.0"
authors = ["iczc <iczcalan@gmail.com>"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]

在Rust中,依赖的代码包被称为crates https://crates.io/ src 放置源代码, 约定:main.rs / lib.rs 是入口文件。--lib可以生成库项目

运行 cargo run 或 cargo build,可执行文件将生成在 target/debug/ 目录,运行 cargo build –release,可执行文件将生成在 target/release/

Basic

变量

Rust 中变量默认是不可变的(immutable),使用 mut 标志为可变(mutable) 静态类型语言 支持类型推导

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// 不可变
let c;
let a = true;
let b: bool = true;
let (x, y) = (1, 2);
c = 12345;

// 可变
let mut z = 5;
z = 6;

不需要追踪一个不可变值如何和在哪可能会被改变

常量

常量用const声明,只能被设置为常量表达式,而不能是函数调用的结果,一般会被编译器内联优化而不分配内存空间。

1
const MAX_POINTS: u32 = 100_000;

函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
fn print_sum(a: i8, b: i8) { // 参数
    println!("sum is: {}", a + b);
}

fn plus_one(a: i32) -> i32 { // 有返回值
    a + 1
    // 等价于 return a + 1
}

fn plus_one(a: i32) -> (i32, i32) { // 使用元组返回多值
    (a, &a + 1)
}

基本数据类型

  • 布尔值(bool)
  • 字符(char) // Unicode
  • 有符号整型(i8, i16, i32, i64, i128)
  • 无符号整型(u8, u16, u32, u64, u128)
  • 浮点数(f32, f64)
  • 数组(arrays),由相同类型元素构成,长度固定。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
let a = [1, 2, 3]; // a[0] = 1, a[1] = 2, a[2] = 3
let mut b = [1, 2, 3];

let c: [int; 3] = [1, 2, 3]; // [类型; 数组长度]

let d: ["my value"; 3]; //["my value", "my value", "my value"];

let e: [i32; 0] = []; // 空数组

println!("{:?}", a); //[1, 2, 3]

动态数组可以用vector,会在堆上分配空间。

  • 元组(tuples),由相同/不同类型元素构成,长度固定。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
let a = (1, 1.5, true, 'a', "Hello, world!");
// a.0 = 1, a.1 = 1.5, a.2 = true, a.3 = 'a', a.4 = "Hello, world!"

let b: (i32, f64) = (1, 1.5);

let (c, d) = b; // c = 1, d = 1.5 // 解构
let (e, _, _, _, f) = a; //e = 1, f = "Hello, world!", _ 作为占位符使用,表示忽略该位置的变量

let g = (0,); // 只包含一个元素的元组 类型注解是可选的

let h = (b, (2, 4), 5); //((1, 1.5), (2, 4), 5)

println!("{:?}", a); //(1, 1.5, true, 'a', "Hello, world!")

使用:a.1

运算符

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
fn main() {
    // 加法
    let sum = 5 + 10;
    // 减法
    let difference = 95.5 - 4.3;
    // 乘法
    let product = 4 * 30;
    // 除法
    let quotient = 56.7 / 32.2;
    // 取余
    let remainder = 43 % 5;
}
  • 比较运算符 == != < > <= >=
  • 逻辑运算符 ! && ||
  • 位运算符 & | ^ « »
  • 赋值运算符
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// 不支持 i++ ++i i-- --i 自增运算符
let mut a = 2;

a += 5; //2 + 5 = 7
a -= 2; //7 - 2 = 5
a *= 5; //5 * 5 = 25
a /= 2; //25 / 2 = 12 not 12.5
a %= 5; //12 % 5 = 2

a &= 2; //10 && 10 -> 10 -> 2
a |= 5; //010 || 101 -> 111 -> 7
a ^= 2; //111 != 010 -> 101 -> 5
a <<= 1; //'101'+'0' -> 1010 -> 10
a >>= 2; //101̶0̶ -> 10 -> 2
  • 类型转换
1
2
let a = 15;
let b = (a as f64) / 2.0; //7.5

控制流

  • if else else if
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
fn main() {
    let number = 6;

    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 x = 5;
    let y = if x == 5 { 10 } else { 15 }; //Rust中if是表达式 返回值可以赋值给变量 类似三元运算符
}
  • match
1
2
3
4
5
6
7
8
9
let tshirt_width = 20;
let tshirt_size = match tshirt_width {
    16 => "S", // check 16
    17 | 18 => "M", // 多值匹配
    19 ... 21 => "L", // 范围匹配
    22 => "XL",
    _ => "Not Available", // default
};
println!("{}", tshirt_size); // L
  • loop // 类似while(1)
1
2
3
4
5
fn main() {
    loop {
        println!("again!");
    }
}
  • while & for
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
let mut a = 1;
while a <= 10 {
	println!("Current value : {}", a);
	a += 1;
}


let a = [10, 20, 30, 40, 50];

    for element in a.iter() {
        println!("the value is: {}", element);
    }

Guessing game

  1. 尝试
$ cargo new guessing_game
$ cd guessing_game
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
use std::io; // 使用标准库io

fn main() { // 定义函数
    /* 一个小游戏 */
    println!("Guess the number!");

    println!("Please input your guess.");

    let mut guess = String::new(); // 使用let创建变量 let foo = bar; new是静态方法

    io::stdin().read_line(&mut guess)
        .expect("Failed to read line"); // 从标准输入读并存到guess变量 read_line返回result枚举类型 后面我们详细说明

    println!("You guessed: {}", guess);
}
  1. 引入外部crate https://crates.io/crates/rand
1
2
[dependencies]
rand = "0.8.2"

cargo build

  1. 生成随机数
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
use std::io;
use rand::prelude::*;

fn main() {
    println!("Guess the number!");

    let secret_number = rand::thread_rng().gen_range(1..100);

    println!("The secret number is: {}", secret_number);

    println!("Please input your guess.");

    let mut guess = String::new();

    io::stdin().read_line(&mut guess)
        .expect("Failed to read line");

    println!("You guessed: {}", guess);
}
  1. 比较结果
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
use std::io;
use std::cmp::Ordering;
use rand::Rng;

fn main() {

    // ---snip---

    let guess: u32 = guess.trim().parse()
    .expect("Please type a number!"); // 字符串转整数
    
    println!("You guessed: {}", guess);

    match guess.cmp(&secret_number) {
        Ordering::Less => println!("Too small!"),
        Ordering::Greater => println!("Too big!"),
        Ordering::Equal => println!("You win!"),
    }
}

Ordering是一个枚举,它的成员是 Less、Greater 和 Equal。这是比较两个值时可能出现的三种结果。 Rust 允许用一个新值来隐藏guess 之前的值。这个功能常用在需要转换值类型之类的场景。它允许我们复用guess变量的名字,而不是被迫创建两个不同变量,诸如 guess_str 和 guess 之类。

  1. 使用循环
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// --snip--
    loop {
        println!("Please input your guess.");

        // --snip--

        match guess.cmp(&secret_number) {
            Ordering::Less => println!("Too small!"),
            Ordering::Greater => println!("Too big!"),
            Ordering::Equal => {
                println!("You win!");
                break;
            }
        }
    }
  1. 处理无效输入
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// --snip--

io::stdin().read_line(&mut guess)
    .expect("Failed to read line");

let guess: u32 = match guess.trim().parse() {
    Ok(num) => num,
    Err(_) => continue,
};

println!("You guessed: {}", guess);

// --snip--

将 expect 调用换成 match 语句,这样不会遇到错误时就崩溃,parse 返回一个 Result 类型,而 Result 是一个拥有 Ok 或 Err 成员的枚举。这里使用的 match 表达式,和之前处理 cmp 方法返回 Ordering 时用的一样。 关于枚举与错误处理,我们后面详细讲到

  1. 最终版
 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
use std::io;
use std::cmp::Ordering;
use rand::prelude::*;

fn main() {
    println!("Guess the number!");

    let secret_number = rand::thread_rng().gen_range(1..100);

    loop {
        println!("Please input your guess.");

        let mut guess = String::new();

        io::stdin().read_line(&mut guess)
            .expect("Failed to read line");

        let guess: u32 = match guess.trim().parse() {
            Ok(num) => num,
            Err(_) => continue,
        };

        println!("You guessed: {}", guess);

        match guess.cmp(&secret_number) {
            Ordering::Less => println!("Too small!"),
            Ordering::Greater => println!("Too big!"),
            Ordering::Equal => {
                println!("You win!");
                break;
            }
        }
    }
}

结构体与面向对象范式

 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
struct Circle {
    x: f64,
    y: f64,
    radius: f64,
}

impl Circle {
    fn new(x: f64, y: f64, radius: f64) -> Circle {
        Circle {
            x: x,
            y: y,
            radius: radius,
        }
    }

    fn area(&self) -> f64 {
        std::f64::consts::PI * (self.radius * self.radius)
    }
}

fn main() {
    let c = Circle { x: 0.0, y: 0.0, radius: 2.0 };
    println!("{}", c.area());

    // use associated function and method chaining
    println!("{}", Circle::new(0.0, 0.0, 2.0).area());
}

关联函数(静态方法) 通常构造函数用此来实现

与Go对比

 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
package main

import (
	"fmt"
	"math"
)

type Circle struct {
	x      float64
	y      float64
	radius float64
}

func NewCircle(x, y, radius float64) *Circle {
	return &Circle{
		x:      x,
		y:      y,
		radius: radius,
	}
}

func (c Circle) Area() float64 {
    return math.Pi * (c.radius * c.radius)
}

func main() {
	c := &Circle{
		x:      0.0,
		y:      0.0,
		radius: 2.0,
	}
    
	fmt.Println(c.Area())

	fmt.Println(NewCircle(0.0, 0.0, 2.0).Area())
}

元组结构体
在参数个数较少时,无字段名称,仅靠下标也有很强的语义时,为每个字段命名就显得多余了。例如:

1
2
3
4
5
struct Color(i32, i32, i32);
struct Point(i32, i32);

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

trait与泛型

用于接口抽象与泛型约束

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
struct Duck;
struct Pig;
trait Fly {
    fn fly(&self) -> bool;
}
impl Fly for Duck {
    fn fly(&self) -> bool {
        return true;
    }
}
impl Fly for Pig {
    fn fly(&self) -> bool {
        return false;
    }
}

fn can_fly<T:Fly>(s: T) -> bool {
    s.fly()
}

泛型

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
fn largest<T>(list: &[T]) -> T {
    let mut largest = list[0];

    for &item in list.iter() {
        if item > largest {
            largest = item;
        }
    }

    largest
}

fn main() {
    let number_list = vec![34, 50, 25, 100, 65];

    let result = largest(&number_list);
    println!("The largest number is {}", result);

    let char_list = vec!['y', 'm', 'a', 'q'];

    let result = largest(&char_list);
    println!("The largest char is {}", result);
}

枚举与错误处理

无参数枚举体

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
enum Number {
    Zero,
    One,
    Two,
}

fn main() {
    let a = Number::One;
    match a {
        Number::Zero => println!("0"),
        Number::One => println!("1"),
        Number::Two => println!("2"),
    }
}

类C枚举体

1
2
3
4
5
enum Color {
    Red = 0xff0000,
    Green = 0x00ff00.
    Blue = 0x0000ff,
}

带参数枚举体

1
2
3
4
5
6
7
8
enum IpAddr {
    V4(u8, u8, u8, u8),
    V6(String),
}

let home = IpAddr::V4(127, 0, 0, 1);

let loopback = IpAddr::V6(String::from("::1"));

使用泛型+枚举的两个示例

Option是标准库中定义的一个非常重要的枚举类型。它表示值的可能性。对Rust而言,变量在使用前必须要赋予一个有效的值,所以不存在空值(Null),如果一个值可能为空,需要显式地使用Option来表示。

1
2
3
4
enum Option<T> { // 通常用T来表示一个泛型,这就不用为每个枚举都定义一种类型
    Some(T), // 有
    None, // 无
}

unwarp可以取出some值,但如果是None会panic,实际是match的语法糖。
expect遇到none值会显示指定的异常消息。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
use std::fmt::Debug;
fn match_option<T: Debug>(o: Option<T>) { // 实现了Debug的trait才可以格式化打印。
    match o {
        Some(i) => println!("{:?}", i), // 当T 为有效值时,才能够从 Some(T) 中取出 T 的值来使用
        None => println!("nothing"),
    }
}

fn main() {
    let a: Option<i32> = Some(3);
    let b: Option<char> = Some("hello");
    let c: Option<u32> = None;
    match_option(a); // 3
    match_option(b); // hello
    match_option(c); // nothing
}

Rust中的错误处理是通过返回Result<T, E>枚举进行的,它表示错误的可能性。

1
2
3
4
enum Result<T, E> {
    Ok(T),
    Err(E),
}

有两个泛型类型T E,它表达成功或失败 用于错误处理 将Result<T, E>作为函数返回值,强制开发者处理OK 和 Err两种类型。

1
2
3
4
5
use std::fs::File;
fn open_file() -> Result<(), std::io:Error> {
    let f = File::open("bar.txt")?; // ?语法糖 在出错时返回std::io:Error
    OK(())
}

Rust内存管理机制

按照内存管理方式可将编程语言大致分为两类:手动内存管理类自动内存管理类。 前者需要开发者手动使用malloc和free等函数显式管理内存,如C。后者使用GC(Garbage Collection, 垃圾回收)来对内存进行自动化管理,如GO, Java。

前者优势性能高,但容易写出内存安全问题的程序,如空指针、悬垂指针、double free等。 后者使用GC接管了开发者管理内存的任务,但为了安全引入了性能负担,GC的时候会引起“世界暂停”,不能作为系统级编程语言(GC是建立在虚拟内存抽象上的)。

有木有一门语言将两者性能结合起来? Rust推崇安全与速度至上,它没有垃圾回收机制,却成功实现了内存安全 (memory safety)。

通过设计一道合理的机制,将内存安全交给编译器解决,没有GC,内存由编译器分配,Rust编译为LLVM IR 其中携带了内存的分配信息,在编译时就可以确定何时需要分配、释放内存。

内存泄漏不在内存安全范围内

Ownership

C语言使用内存

1
2
3
4
5
6
7
8
#include <stdio.h>
int main()
{
    int *p = (int *)malloc(100)
    printf("%p", p);
    free(p);
    return 0;
}

为缓解此问题C++引入了智能指针,即通过智能指针来描述所有权,实现了内存的半自动化管理。

1
2
3
4
5
6
7
8
9
#include <iostream>
#include <memory>
using namespace std;
int main()
{
    unique_ptr<int> p(new int(5));
    cout << *p << endl;
    return 0;
}

使用变量的生命周期绑定资源的使用周期,这种资源管理的方式有一个术语叫RAII(Resource Acquisition Is Initialization),智能指针就是基于RAII机制来实现的。 在现代C++中RAII的机制是使用构造函数来初始化资源,使用析构函数来回收资源(在栈对象析构函数中释放堆内存)。

存在安全隐患的示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#include <iostream>
#include <memory>
using namespace std;
int main()
{
    unique_ptr<int> p(new int(5));
    cout << *p << endl;
    auto other_p = move(p);
    cout << *p << endl;
    return 0;
}

空指针解引 segmehtation fault 语义上的 move 并没有静态检查

Rust在此基础上更进一步,将所有权的概念融入到语言中

1
2
3
4
5
6
fn main() {
    let p = Box::new(5); // 堆上分配内存
    println!("{}", *p);
    let other_p = p;
    println!("{}", *p);
}

编译不过

rust 数据默认move语义, 实现了drop trait,栈元素离开作用域时自动调用drop析构函数,用以释放指向的堆内存。

1
2
3
trait Drop {
    fn drop(&mut self);
}

什么样的数据不是move语义呢:除非它实现了 Copy trait, 即copy语义,什么样的数据类型会被实现copy语义呢?

值类型 存在栈上 如int bool等,值类型执行赋值操作时会自动赋值一个副本。

引用类型 数据存在堆上,栈上只存放指向堆中数据的地址(指针),如可变长字符串,vector。

1
2
3
4
5
fn main() {
    let x = 5;
    let y = x; // 按位复制(栈复制)后,与原始数据无关,修改后不影响原值,不存在内存安全问题
    // Rust不允许引用类型栈复制(两个指针指向同一个堆空间) 存在潜在的内存安全问题 因为引用类型默认move语义
}

会按位复制(栈复制) 是copy语义 实现了Copy trait,一旦某类型实现copy trait,那么它在变量绑定、函数参数传递、函数返回值等场景下都是copy语义,而不是默认的move语义。

并不是所有的类型都可以手动实现Copy trait,如为结构体实现 Copy trait,需该结构体的所有类型都是实现Copy trait的。实现了drop析构函数的是不能实现Copy trait的。

Rust通过此标记对值语义和引用语义做了精准分类,来帮助编译器检测潜在的内存安全问题。所有权管理堆内存,即引用类型,此类型才需要保证内存安全。

对于copy类型 按位复制不会出现安全问题,对于禁止实现copy的类型,按位复制可能出问题,所以默认实现move语义,且通过RAII机制自动释放堆内存。 所有权规则:

  1. 堆内存上的数据类型都有一个称为所有者的变量(栈上)
  2. 任一时刻(生存周期)有且只有一个所有者
  3. 当所有者变量离开作用域,堆上的数据被自动释放
1
2
3
4
5
6
{
    let s = String::from("hello"); // 从此处起,s 是有效的

    // 使用 s
}                                  // 此作用域已结束,
                                   // s 不再有效

1
2
3
4
let s1 = String::from("hello");
let s2 = s1; //一个值的所有权被转移给另外一个变量绑定的过程就叫所有权转移

println!("{}, world!", s1); // use of moved value: `s1`

// 想复制堆上数据 可以用clone

所有权与函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
fn main() {
    let s = String::from("hello");  // s 进入作用域

    takes_ownership(s);             // s 的值移动到函数里 ...
                                    // ... 所以到这里不再有效

    let x = 5;                      // x 进入作用域

    makes_copy(x);                  // x 应该移动函数里,
                                    // 但 i32 是 Copy 的,所以在后面可继续使用 x

}

fn takes_ownership(some_string: String) { // some_string 进入作用域
    println!("{}", some_string);
} // 这里,some_string 移出作用域并调用 `drop` 方法。占用的内存被释放

fn makes_copy(some_integer: i32) { // some_integer 进入作用域
    println!("{}", some_integer);
} // 这里,some_integer 移出作用域。不会有特殊操作

返回值与所有权

 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
fn main() {
    let s1 = gives_ownership();         // gives_ownership 将返回值
                                        // 移给 s1

    let s2 = String::from("hello");     // s2 进入作用域

    let s3 = takes_and_gives_back(s2);  // s2 被移动到
                                        // takes_and_gives_back 中,
                                        // 它也将返回值移给 s3
} // 这里, s3 移出作用域并被丢弃。s2 也移出作用域,但已被移走,
  // 所以什么也不会发生。s1 移出作用域并被丢弃

fn gives_ownership() -> String {             // gives_ownership 将返回值移动给
                                             // 调用它的函数

    let some_string = String::from("hello"); // some_string 进入作用域.

    some_string                              // 返回 some_string 并移出给调用的函数
}

// takes_and_gives_back 将传入字符串并返回该值
fn takes_and_gives_back(a_string: String) -> String { // a_string 进入作用域

    a_string  // 返回 a_string 并移出给调用的函数
}

主动释放

1
2
3
4
5
6
7
use std::mem::drop;

fn main() {
    let mut v = vec![1, 2, 3]; // <--- v的生命周期开始
    delete(v);                   // ---> v的生命周期结束
    v.push(4);                 // 错误的调用
}
1
2
3
fn drop<T>(_x T) {
    
}

思考:标准库中的std::mem::drop函数是怎样实现的呢?

1
2
3
4
5
6
7
8
use std::mem::drop;

fn main() {
    let x = 1_i32;
    println!("before drop {}", x);
    drop(x);
    println!("after drop {}", x);
}

Borrow(reference)

在每一个函数中都获取所有权并接着返回所有权太繁琐。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
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() 返回字符串的长度

    (s, length)
}

如果我们想要函数使用一个值但不获取所有权该怎么办呢? 定义一个新的calculate_length 函数,它以一个对象的引用作为参数而不是获取值的所有权:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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()
}

取指针被称为借用, 语义上告诉编译器 没有对这块内存的所有权,只是借来用,离开作用域后不会drop堆上内存,且要归还。

可变与不可变借用

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

    change(&s);
}

fn change(some_string: &String) {
    some_string.push_str(", world");
}

cannot borrow immutable borrowed content *some_string as mutable 正如变量默认不可变,借用默认也不可修改

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) { // 可变引用
    some_string.push_str(", world");
}

借用有以下几个规则:

  • &mut型借用只能指向本身具有mut修饰的变量,对于只读变量,不可以有&mut型借用。
  • &mut型借用指针存在的时候,被借用的变量本身会处于“冻结”状态。
1
2
3
4
5
6
let mut s = String::from("hello");

let r1 = &mut s;
s.push_str(", world");
println!("{}, {}", r1, s);

  • 借用指针不能比它指向的变量存在的时间更长。 避免悬垂引用
1
2
3
4
5
6
7
8
9
fn main() {
    let reference_to_nothing = dangle();
}

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

    &s
}
  • 如果只有&型借用指针,那么能同时存在多个;如果存在&mut型借用指针,那么只能存在一个;一个可变引用不能与其他引用同时存在 避免数据竞争
1
2
3
4
5
6
7
let mut s = String::from("hello");

let r1 = &s; // 没问题
let r2 = &s; // 没问题
let r3 = &mut s; // 大问题

println!("{}, {}, and {}", r1, r2, r3);
  • 一个引用的作用域从声明的地方开始一直持续到最后一次使用为止
1
2
3
4
5
6
7
8
9
let mut s = String::from("hello");

let r1 = &s; // 没问题
let r2 = &s; // 没问题
println!("{} and {}", r1, r2);
// 此位置之后 r1 和 r2 不再使用

let r3 = &mut s; // 没问题
println!("{}", r3);

可变引用保证了在写的时候没有任何指针可以读取该值的内存,不可变引用保证了内存不会在读取之后被写入新数据。

核心原则

共享不可变,可变不共享 Rust不是针对各式各样的场景,用case by case的方式来解决内存安全问题。而是通过一种统一的机制,高屋建瓴地解决这一类问题。 多个读同时存在是可以的,存在一个写的时候,其他的读写都不能同时存在。 &mut型借用也经常被称为“独占指针”, &型借用也经常被称为“共享指针”。

例1 共享不可变一定安全

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

let p1 = &s;
let p2 = &s
println!("{}, {}", p1, p1);

例2 共享后不可变

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

let r1 = &mut s;
s.push_str(", world");
println!("{}, {}", r1, s); // r1是这时唯一可以操作内存的入口

例3 同时多个可变引用

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

let mut p1 = &s;
let mut p2 = &s
println!("{}, {}", p1, p1);

例4 与cpp对比的一个🌰

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#include <iostream>
#include <vector>
using namespace std;

int main()
{
    vector<int> v(5, 5);
    int *p = &v[0];
    cout << *p << endl;
    for (int i = 0; i < 100; i++)
    {
        v.push_back(i);
    }
    
    cout << *p << end;
    return 0;
}
1
2
3
4
5
6
7
8
fn main() {
    let mut arr : vec<i32> = vec! [5, 5, 5, 5, 5]
    let p : &i32 = &arr[0];
    for i in 1..100 {
        arr.push(i);
    }
    print!(p)
}

error: cannot borrow arr as mutable be cause it is also borrowed as immutable 在存在一个不可变引用时不能修改原变量的值