Induction: Rust
进入 Rust 的世界
进入 Rust 的世界
curl https://sh.rustup.rs -sSf | sh 命令安装 rust 工具集。rustup update 更新工具集。rustc --version 检查当前命令行版本。rustup doc 查看文档离线版。cargo new 创建一个 Rust 项目,在项目路径下可以执行 cargo build 编译,执行 cargo run
运行,执行 cargo check 检查是否有编译错误。cargo doc --open 可以查看依赖的文档。let 声明的 Rust 的变量默认是不可变的,不能对不可变变量进行二次赋值。mut 关键字可以使其可变。const 声明常量,通常约定使用下划线分隔的全大写字母来命名一个常量。_ 开头的变量名,使 rust 忽略未使用变量的警告fn main(){
let x = 5;
let x = x + 1;//6
let mut x = 7;
x = 8; // 8
const M = 6;
}
let (x, y, z) = (500, 6, 3.5);x.0let a = [3; 5] 这样写。a[0]-> type 声明类型。// 添加代码注释if
if 的条件必须是 bool 类型
fn main() {
let number = if x > 5 {
1
} else {
2
}
}
if 是表达式,可以通过 if 为变量赋值。如果使用条件赋值,if 必须返回同一种类型的值。
loop: 使用 break 跳出 loop 循环
while n < 5
for item in collections
drop 函数释放内存。s1.clone() 方法可以深拷贝& 将变量标记为引用,允许在不获取所有权的情况下使用该值。&mut String 的类型声明,标记为可变引用。let slice = &s[1..5] 使 slice 保留了对 s 的不可变引用,struct User {
email: String,
username: String,
active: bool,
sign_in_count: u64,
}
let user1 = User {
email: String::from("user2@example.com"),
username: String::from("user2"),
active: true,
sign_in_count: 0,
}
let user2 = User {
email: String::from("user2@example.com"),
username: String::from("user2"),
..user1
}
.. 将未显式声明的字段展开复用,需要在末尾位置使用struct User {
username: String
}
impl User {
fn get_name(&self) {
self.username
}
fn hello() {
println!("hello");
}
}
fn 及方法名称声明,第一个参数永远是 self,通常一般定义为 &self。impl 中定义。impl 中不需要定义 &self 参数的函数称为关联函数,通过 User::hello() 调用。impl 中 定义enum IpAddress {
v4,
v6
}
enum IpAddress {
v4(String),
v6(String),
Color(i32, i32, i32)
}
enum 声明枚举,枚举值可以直接声明,也可以附加其他类型的数据值。impl 声明方法。Option<T> 的枚举值 None 实现类似空值的能力,同时 Option<T>
有值时的枚举类型是 Some<T>。fn value_in_cents(coin: Coin) -> u32 {
match coin {
Coin::Penny => {
println!("Lucky penny!");
1
},
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
_ => ()
}
}
match 会自上而下匹配模式match 必须处理所有的情况。当不需要罗列所有情况时,使用 _ 作为通配符处理其他情况if let 用于简单控制流,类似于 match 只处理一种模式时的情况。包(package):一个用于构建、测试并分享单元包的 Cargo 功能。
单元包(crate):一个用于生成库或可执行文件的树形模块结构。
模块(module)及 use 关键字:它们被用于控制文件结构、作用域及路径的私有性。
路径(path):一种用于命名条目的方法,这些条目包括结构体、函数和模块等。
package 最多拥有一个库单元包,可以拥有任意个二进制单元包,至少包含一个单元包。将 src/main.rs 视作二进制单元包根节点且与包名称一致,将 src/lib.rs 视作库单元包根节点。
以 mod 定义了一个模块,模块中可以定义新的模块。
Rust 中的函数、方法、struct、enum、mod、常量都是默认私有的。
通过 pub 可以标记为公有的。
通过绝对路径 crate::count::one::one_to() 从根节点或相对路径 one::one_to()
相对于当前文件访问模块,更倾向于使用绝对路径。
模块内子函数使用 super 关键字如 super::get_order() 访问外层函数的方法。
fn serve_order() {}
mod back_of_house {
fn fix_incorrect_order() {
cook_order();
super::serve_order();
}
fn cook_order() {}
}
对于 模块、结构体 pub 标记的部分才会成为公有的,结构内部的字段或方法仍需要再次声明
pub。但枚举一旦声明公开,则所有枚举项都成为公开的。
use crate::front_of_house::hosting::add_to_waitlist;
use self::front_of_house::hosting;
use std::fmt::Result;
use std::io::Result as IoResult;
use std::{cmp::Ordering, io};
use std::io::{self, Write};
use std::io::*
use 将路径导入作用域。指定相对路径时,需要使用 self:: 开头use
模块函数时引入函数的上一层,这样可以体现出是模块方法。引入 struct 或 enum 时,则直接 use
到目标。as 将引入的关键字进行重命名。use 导入的关键字进行 pub 标记。use 同一个包内的多个模块时,可以使用 {} 将同一大模块下的子模块合并。或使用 *
通配符导入所有子模块,但尽量谨慎使用。let v1: Vec<i32> = Vec::new();
let v2 = vec![1, 2, 3];
for i in &v {
}
for i in &mut v {
*i += 50
}
Vec::new() 创建新的 vector,或者通过宏 vec! 携带初始值声明,Rust 可以自动推导出类型。.push() 添加值v.get(2) 会返回 Option<&T> 类型的值,另一种获取元素的方法 &v[2]
如果索引超出了边界则会直接触发 panicfor in 遍历 vector。// 初始化方法
let mut s1 = String::new("initial contents");
let s2 = "initial contents".to_string();
// 添加字符
s1.push_str("bar")
// + 符号拼接
let s1 = String::from("Hello, ");
let s2 = String::from("world!");
let s3 = s1 + &s2; // 注意这里的s1已经被移动且再也不能被使用了
// 使用 format 拼接
let s = format!("{}-{}-{}", s1, s2, s3);
String::new() 和 "initial".to_string() 两种初始化方式没有区别。push_str 添加字符串+ 拼接字符串,或者通过 format!() 拼接, format! 不会获取所有权。for c in "".chars() {} 或 for b in "".bytes() 遍历字符串。use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
// 通过 collect 初始化
let teams = vec![String::from("Blue"), String::from("Yellow")];
let initial_scores = vec![10, 50];
let scores: HashMap<_, _> =
teams.iter().zip(initial_scores.iter()).collect();
// get
let team_name = String::from("Blue");
let score = scores.get(&team_name);
// 遍历
for (key, value) in &scores {
}
HashMap::new() 创建 HashMap,或使用 collect() 初始化。.get() 方法,同样获取到了 Option<&V> 类型for (key, value) in &h 遍历。use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Blue"), 25);
scores.entry(String::from("Blue")).or_insert(50);
let score = scores.entry(String::from("Blue")).or_insert(60);
*score += 1
insert 同一个 key 覆盖值,通过 .entry() 返回 Entry 类型枚举检查是否已有值。panic! 会导致程序中断let f = File::open("hello.txt");
let f = match f {
Ok(file) => file,
Err(error) => {
panic!("There was a problem opening the file: {:?}", error)
},
}
// 返回错误
match f.read_to_string(&mut s) {
Ok(_) => Ok(s),
Err(e) => Err(e),
}
match 处理可恢复错误 Result<T, E>unwrap 在正确时返回值,错误时默认调用 panic!。 expect 与 unwrap
功能类似,但可以带上一段错误提示信息。Err(e) 包装将错误返回给用户自由处理,使用 ? 运算符可以简写。 ?
如果遇到错误会提前结束这个函数,并返回 Err 类型给调用者。如果正确则继续向下执行。 ?
只能用于处理返回 Result 的函数// struct 范型
struct Point<T> { x: T, y: T, }
// enum 泛型
enum Result<T, E> { Ok(T), Err(E), }
// fn 泛型
impl<T> Point<T> { fn x(&self) -> &T { &self.x } }
pub trait Summary {
fn summarize(&self) -> String;
// 提供默认实现
fn summarize_author(&self) -> String {
format!("(Read more from {}...)", self.summarize())
}
}
struct NewsArticle {}
// 通过 impl 在 struct 中实现 trait
impl Summary for NewsArticle {
fn summarize(&self) -> String {
format!("{}, by {} ({})", self.headline, self.author, self.location)
}
}
// trait 作为参数
pub fn notify(item: impl Summary) {
println!("Breaking news! {}", item.summarize());
}
// trait bound
pub fn notify<T: Summary>(item1: T, item2: T) {
println!("Breaking news! {}", item.summarize());
}
// 多个 trait
fn some_function<T: Display + Clone, U: Clone + Debug>(t: T, u: U) -> i32 {
// 效果相同
fn some_function<T, U>(t: T, u: U) -> i32
where T: Display + Clone, U: Clone + Debug
{
// 返回 trait 类型
fn returns_summarizable() -> impl Summary {
// 实现了 Display 和 PartialOrd 的 Pair 才有 cmp_display 方法
impl<T: Display + PartialOrd> Pair<T> {
fn cmp_display(&self) {}
}
impl Trait 是 trait bound 的语法糖&i32 // 引用
&'a i32 // 带有显式生命周期的引用
&'a mut i32 // 带有显式生命周期的可变引用
// 通过生命周期标注 拥有相同的生命周期
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
// struct 生命周期标注
struct ImportantExcerpt<'a> {
part: &'a str,
}
// 方法定义中的 生命周期标注
impl<'a> ImportantExcerpt<'a> {
fn level(&self) -> i32 {
3
}
}
fn foo<'a>(x: &'a i32) -> &'a i32。&self 或
&mut self,说明是个对象的方法(method),那么所有输出生命周期参数被赋予 self
的生命周期。'static,其生命周期能存活于整个程序期间。fn main() {}
#[cfg(test)] // 标注为测试函数
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}
assert! 宏接受 true 则通过测试,反之 接受 false 则失败assert_eq! 测试相等, assert_ne! 测试不相等。assert! 宏第二个参数可以传递报错信息。#[should_panic] 属性标注函数应该 panic,通过 #[should_panic(expect = "error message")]
提示 panic 信息。Result<T, E> 也可以用于测试函数正确、失败cargo test 运行测试, cargo test [命令行参数] -- [二进制文件参数]cargo test -- --show-output 显示代码打印内容cargo test i_am_fn_name 测试单个函数, cargo test i_am 测试 i_am 开头的函数#[ignore] 标记忽略测试该函数,再通过 cargo test -- --ignored
可以忽略标记了 ignore 的函数。#[cfg(test)] 标注函数,只在 cargo test 执行函数。let example_closure = |x| x;
let expensive_closure = |num| {
// ...
num
};
// 结合泛型声明闭包 T
struct Cacher<T>
where T: Fn(u32) -> u32
{
calculation: T,
value: Option<u32>,
}
// 实现 Cacher ,当访问 cacher.value 时才会执行闭包计算并缓存到 value 属性上
impl<T> Cacher<T>
where T: Fn(u32) -> u32
{
fn new(calculation: T) -> Cacher<T> {
Cacher {
calculation,
value: None,
}
}
fn value(&mut self, arg: u32) -> u32 {
match self.value {
Some(v) => v,
None => {
let v = (self.calculation)(arg);
self.value = Some(v);
v
},
}
}
}
|a| 定义闭包。Fn , FnMut , FnOnce trait 和 泛型在 struct 中使用闭包pub trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
}
Iterator 的 trait, next 是唯一被要求定义的方法。 next
一次返回迭代器中的一个值 Some<T>,结束时返回 Nonenext 调用中得到的值是 vector 的不可变引用Deref 和 Drop trait。enum List {
Cons(i32, List),
Nil,
}
let list = Cons(1, Cons(2, Cons(3, Nil))); // error! 因为不知道需要分配多大的内存
let list = Cons(1,
Box::new(Cons(2,
Box::new(Cons(3,
Box::new(Nil)))))); // pass!
Box<T> 将数据储存在堆上,更适合用于递归类型的数据场景。*y 完成。use std::ops::Deref;
struct MyBox<T>(T);
impl<T> Deref for MyBox<T> {
type Target = T;
fn deref(&self) -> &T {
&self.0
}
}
Deref trait 实现,实现了 Deref
的函数可以通过解引用强制转换的能力转换值的类型并返回。
T: Deref<Target=U> :从 &T 到 &U。T: Deref<Target=U> :从 &mut T 到 &U。T: DerefMut<Target=U> :从 &mut T 到 &mut U。struct CustomSmartPointer {
data: String,
}
impl Drop for CustomSmartPointer {
fn drop(&mut self) {
println!("Dropping CustomSmartPointer with data `{}`!", self.data);
}
}
fn main() {
let c = CustomSmartPointer { data: String::from("some data") };
println!("CustomSmartPointer created.");
drop(c);
println!("CustomSmartPointer dropped before the end of main.");
}
Drop trait 来执行一些代码。std::mem::drop 可以提前清理值。Rc<T> 分配多所有权, Rc::new() 创建引用, Rc::clone()
复制引用,当引用计数为 0 时会清理内存并处罚 Drop 。Rc<T> 和 RefCell<T> 可能使引用计数到不了 0,从而创建循环引用。通过 thread::spawn 创建新线程,返回值可以通过 .join() 方法等待所有线程结束。
通过 move 转移所有权。
通过 sync::mpsc::channel 创建新通道,返回了新元组 (tx, rx) 分别是发送端和接受端。
tx.send() 函数获取其参数的所有权并移动这个值归接收者所有。
tx.clone() 可以返回新的发送者, mpsc 的是 mutiple producer 的缩写可以有多个发送者。
use std::sync::Mutex;
fn main() {
let m = Mutex::new(5);
{
let mut num = m.lock().unwrap();
*num = 6;
}
println!("m = {:?}", m);
}
Mutex::new() 创建互斥器,用 lock
方法获取锁,以访问互斥器中的数据。这个调用会阻塞当前线程。