# The Rust Programming Language

这本书可以在官网免费阅读英文版 (opens new window),在微信读书阅读中文版《Rust权威指南》。

# 3. Common Programming Concepts

# 3.1. Variables and Mutability

# 3.2. Data Types

# 3.3. Functions

# 3.4. Comments

# 3.5. Control Flow

# 4. Understanding Ownership

# 4.1 What is Ownership?

# The Stack and the Heap

stack heap
内存分配方式 值直接存在函数调用栈帧上,移动栈顶指针就可以分配内存 值存在堆上面,值的引用(指针)存在栈上面,需要通过系统调用申请堆上的内存
内存清理方式 函数return时,栈帧直接被回收 需要通过系统调用回收内存,少调内存泄露,多调程序崩溃
大小 只有编译期确定大小的值能存在栈上,如基本数据类型、指针等
速度 指针跳转存在开销、堆上存放比较分散不利于CPU缓存、内存清理需要系统调用

# Ownership Rules

这三个规则极其重要

  • Each value in Rust has a variable that’s called its owner.
  • There can only be one owner at a time.
  • When the owner goes out of scope, the value will be dropped.

# Variable Scope

变量作用域的概念和其它语言是一样的。

# The String Type

后续的 demo 将通过 String::from() 创建字符串来研究 Ownership,由于其大小是动态的所以肯定存在堆上。

let s = String::from("hello");
s.push_str(" world");

# Memory and Allocation

放在堆上的内存需要释放,如果像 C 语言那样由程序员手动释放的话,很容易出错。多释放会导致程序崩溃,少释放会导致内存泄露。

所以目前大多语言都实现了 GC,不需要程序员手动管理内存。

但 Rust 和其它语言不太一样,是在变量离开作用域的时候自动释放的,这一点其实借鉴了 C++ 的 RAII。

{
  let s = String::from("hello");
} // s 离开作用域了,Rust 会自动调用 s.drop() 方法

这套机制看起来很简单,但实际情况比这更加复杂,特别是多个变量引用同一个堆内存的时候。所以 Rust 要引入 Ownership 的概念,这也是我们接下来要学习的重点。

# Move, Clone, Copy

这部分内容请直接看书。

# Function

这部分内容请直接看书。

# 4.2. References and Borrowing

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();
    (s, length)
}

上面这段代码为了让 main 函数继续拥有 ownership 写的太冗长了,其实可以使用 Reference,这可以避免 ownership move。代码如下,这个操作叫做 borrowing。

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 is a reference to a String
    s.len()
} // Here, s goes out of scope. But because it does not have ownership of what
  // it refers to, nothing happens.

# Mutable References

接下来书上介绍了可变引用,以及可变引用的限制。

But mutable references have one big restriction: you can have only one mutable reference to a particular piece of data in a particular scope. This code will fail:

这个限制是为了能在编译期杜绝 data races,这个概念跟 race condition 有点类似。以下三个要求同时满足时会发生 data races。

  • Two or more pointers access the same data at the same time.
  • At least one of the pointers is being used to write to the data.
  • There’s no mechanism being used to synchronize access to the data.

下面这段代码有多个不可变引用,但不会导致 data races,可以通过编译。

let mut s = String::from("hello");

let r1 = &s; // no problem
let r2 = &s; // no problem

下面这段代码,因为同时存在不可变引用和可变引用,可能会导致 data races,所以不能通过编译。

let mut s = String::from("hello");

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

# Dangling References

悬挂指针是访问一个已经被释放的内存地址。

下面这段代码是有问题的,Rust 也会在编译期检查到悬挂指针问题而报错。

fn main() {
    let reference_to_nothing = dangle();
    // reference_to_nothing 指向一个已经被释放的内存
}

fn dangle() -> &String {
    let s = String::from("hello");
    &s
}  // 返回的是引用,不会把 ownership 转移出去,所以变量 s 是 owner
   // 当 s 离开作用域了,会释放 "hello" 字符串的内存

# 4.3 The Slice Type

直接看原文吧。

# 5. Structs

# 5.1 Defining and Instantiating Structs

这部分比较简单,自己看原文学吧。

# 两种语法糖

  • Field Init Shorthand
  • Struct Update Syntax

# 两种特别的结构体

  • Tuple Structs 可以创建没有字段名的结构体
  • Unit-Like Structs 可以创建没有字段的结构体

# Ownership & Lifetime

下面这段代码会报错,因为 email: &str 是 borrow (借用)。改成 email: String 就可以了,因为这是 move (挪用)。如果不想改 &str 那么就要用 lifetime 了,这个知识后面的章节会讲。

struct User {
    username: &str,
    email: &str,
    sign_in_count: u64,
    active: bool,
}

fn main() {
    let user1 = User {
        email: "someone@example.com",
        username: "someusername123",
        active: true,
        sign_in_count: 1,
    };
}

# 5.2 An Example Program Using Structs

# 5.3 Method

# Defining Methods

Rust 定义 method 的语法跟 Python 有点类似,第一个参数一定是 self。

#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    // 最常见的写法,take ownership
    fn area1(&self) -> u32 {
        self.width * self.height
    }

    // 等价于 area1
    fn area2(self: &Rectangle) -> u32 {
        self.width * self.height
    }

    // take ownership
    fn area3(self) -> u32 {
        self.width * self.height
    }

    // 等价于 area3
    fn area4(self: Rectangle) -> u32 {
        self.width * self.height
    }
}

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

    println!("{}", rect.area1());
    println!("{}", rect.area2());
    println!("{}", rect.area3());
    println!("{}", rect.area4()); // error[E0382]: use of moved value: `rect`
}

# Associated Functions

定义的时候没有 self 参数,调用的时候用双冒号 Rectangle::square(3);,跟其它语言的静态方法很像。