Rustbook Notes

变量

变量默认都是不可变的, 需要 mut 声明来指定可变变量. 依然有常量 const 定义常量, 全大写 snake 风格, 和 c 保持一致. 常量无法声明为 mut, 作用于可以全局, 常量和不可变变量的区别就在这里.

不可变变量不能被修改, 但可以被隐藏, 隐藏就是允许重新定义了一个同名常量, 那么第一个就被隐藏了. 在数据类型转换时候真的非常贴心, 这样就不用定义 a_str 之类的变量了, 虽然名字相同, 但完全是建了一个新的变量.

变量分为标量(scalar)和复合类型(Compound types)两种.

标量(scalar)有4种: 整型、浮点型、布尔类型和字符类型

复合类型(Compound types)有两个原生的: 元组(tuple)和数组(array), 潜台词自然是可以建立自己的复合类型了.

可以简单认为标量(scalar)都是栈存储的, 复合类型(Compound types)tuple 和 array 里存储的值全是栈存储的时候, 也被当做栈存储的.

而 vector 等动态扩展的复合类型自然是堆存储的

函数

fn five() -> i32 {
    5
}

-> 来声明返回值类型

最后一句如果是返回值, 那么不加分号就可以少些个 return

函数可以接收转移所有权的变量, 也可以接收借用(borrowing)的变量. 这里概念后面说.

控制流

if 被当做一个表达式, 表达式是可以给变量赋值的

    let number = if condition {
        5
    } else {
        6
    };

表达式返回值必须是一致的数据类型.

loop 的返回值就是放在 break

while 可以当做 loop 的一个语法糖, 直接好了退出条件, 但是没法指定返回值, 也没法当做表达式给某个变量赋值了.

for 的语法和 python 一样, 直接 in 一个迭代器, 比 go 那种传统的方式简洁多了

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

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

所有权

以前弄 c 还要自己构建引用计数指针来清除不用的变量.

rust 想了一个更优雅的方式, 相当于引用计数永远是 1 或者 0, 一旦变量离开作用域就清零回收了

所有堆存储的数据类型的变量, 其实都是指向那个堆的指针.

rust 设定”引用计数只有 1”, 那么两个变量都指向同一个引用, 假如都释放时候, 岂不是要对一个堆进行两次释放了?

rust 的处理更巧妙, 一个堆存储的变量, 给一个另一个变量负值的时候, 自身就失效了, 丧失了对堆的有效引用. 这个可以被称作所有权的转移. 所以下面的代码会报错.

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

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

随着所有权的转移, 永远保证只有一个有效的堆引用, 简直优雅得不得了.

变量传递给函数也同样丧失了所有权.

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

    let s3 = takes_and_gives_back(s2);  // s2 被移动到

函数结束时候, 如果没有返回, 那么这个引用计数相当于 0, 会导致堆资源释放. 如果想继续处理, 那么得把变量返回, 外部用另外一个变量来接收所有权.

引用

这样的代码未免太不优雅了. 得有一种能告知函数参数的值, 又不把所有权移交给函数的方式: 引用

引用可以理解为指向指针的指针:

trpl04-05.svg

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

let len = calculate_length(&s1);

把这样的值送入函数, 称之为借用(borrowing), 所有权没有转移, 函数也完全无权限修改. 这个方式和很多语言相反的, 比如 go, 向函数传递指针, 函数的修改才能有效的影响外部的变量.

要在函数内修改传入值的时, 需要使用可变引用.

首先变量得是 mut 的, borrowing 时也声明为可变.

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

    change(&mut s);

同一个作用域内可变引用只能有一个, 毕竟一个东西不能借两次嘛

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

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

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

这个代码会报错, 可见 rust 谨慎的保持唯一性, 极力避免数据竞争(data race)

  • 不同作用域可以各有可变引用
  • 只读引用可以有无数个
  • 通作用域不能同时有可变引用和只读引用

引用的作用域有两种结束方式:

  1. 离开代码块
  2. 最后一次使用

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

悬垂引用(Dangling References) 其实就是 c 中野指针的概念, rust 编译器会做检查来避免出现悬垂引用

字符串 slice 结构上就是指向字符串对应两个位置的引用, 所以天然不具备所有权.

这个报错的代码要注意

fn main() {
    let mut s = String::from("hello world");

    let word = first_word(&s);

    s.clear(); // error!

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

clear 操作本质上是获取一个可变引用, 这个情况下和只读引用 word 在同一个作用域, 因为引用和作用域的规则, 编译报错了; 规则天然就保证了不会出现使用一个被 clear 了的 slice 的情况. 优雅.

定义字符串的误解:

let s = "Hello, world!";

这样定义的是一个 String slice 变量, 这个类型是 &str

let s = String::from("Hello, world!");

这样啰嗦的定义的才是一个 String 变量

一个 mut 的 slice 能不能对其修改呢?

结构体

参数名相同时候, 可以简化 k:v 的命令实例化方式:

fn build_user(email: String, username: String) -> User {
    User {
        email,
        username,
        active: true,
        sign_in_count: 1,
    }
}

希望修改已有实例的部分字段, 生成新的实例时候(类似继承), 可以有简化方式:

let user2 = User {
    email: String::from("[email protected]"),
    username: String::from("anotherusername567"),
    ..user1
};

会自动用 user1 填充剩下为指定的字段

打印结构体

debug 时候会想打印结构体出来看看

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

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

    println!("rect1 is {:#?}", rect1);
}

结构体要加入显示的开关 #[derive(Debug)] 来允许打印

占位符还要指定使用Debug的输出模式: {:?} 或者 {:#?}

结构体方法是定义在结构体里面的方法, 书中叫方法语法; 第一个参数是默认传入的 &self

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

impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }
}

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

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

如果第一个参数不是 &self 的函数叫做关联函数(associated functions), 就是静态方法或者类方法的概念. 是结构体无需实例化就能调用的函数方法, 通常都是一些构造函数.

impl Rectangle {
    fn square(size: u32) -> Rectangle {
        Rectangle { width: size, height: size }
    }
}

使用早就见过了, 还记得如何初始化一个 String 类型么?

let sq = Rectangle::square(3);
let s1 = String::from("hello");

一样的使用方式