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, 会导致堆资源释放. 如果想继续处理, 那么得把变量返回, 外部用另外一个变量来接收所有权.
引用
这样的代码未免太不优雅了. 得有一种能告知函数参数的值, 又不把所有权移交给函数的方式: 引用
引用可以理解为指向指针的指针:
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)
- 不同作用域可以各有可变引用
- 只读引用可以有无数个
- 通作用域不能同时有可变引用和只读引用
引用的作用域有两种结束方式:
- 离开代码块
- 最后一次使用
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");
一样的使用方式