google comprehensive-rust 4days 🔗
较短时间4 days入门。
摘要:
standard library
- Option, Result, error handling
- String
- Vec
- HashMap, config hash algorithm
- Box: an owned pointer for heap-alloc data
- Rc: a shared reference counted pointer for heap-alloc data
重要的traits
Iterator , IntoIterator(for in)
From
shared state 共享内存的状态
- Arc
: share read-only access via clone() - Mutex
: allow mutable access to T behind a read-only interfz Arc<Mutex<Vec<i32>>>
Send & Sync
- T: send, T can move to thread. T alloc at t1 and de-alloc at t2
- T: Sync, &T can move to thread. only if &T: Send.
- Send + Sync
- 大部分类型都是 i32, char, &str, String, Arc
, Mutex AtomicBool
- 大部分类型都是 i32, char, &str, String, Arc
- Send + !Sync
- interior mutability
- mpsc::Sender
, mpsc::Receiver - Cell
RefCell
- !Send + Sync
- MutexGuard
, alloc at t1 and should de-alloc at t2
- MutexGuard
- !Send + !Sync
- Rc
- *const T, *mut T
- Rc
《The Rust Programming Language》 🔗
比较全面,看完需要较长时间
1 开始(开发环境,文档) 🔗
- rustup.rs安装rustc, cargo, rustfmt, etc...
- rustup update 更新rustc版本, rustc --version
- rustup doc // offline book
- rustup docs --book 会打开file:///Users/brett/.rustup/toolchains/nightly-aarch64-apple-darwin/share/doc/rust/html/book/index.html
- IDE support via
rust-analyzer - docs.rs 可以看std库的api
- cargo --version, cargo check(make sure it compiles and not gen exefile)
看书时的小彩蛋, 3种螃蟹🦀️图标具有特殊含义(不能编译,运行时panic,不是预期行为)。
cargo 🔗
cargo home: 在$HOME/.cargo/中存储项目的依赖。 其中,/bin:cargo install安装的程序。 /git: 对github仓库的依赖。 /registry: 对crate.io仓库的依赖。
Cargo.toml清单的格式。[workspace]: 集成多个package一起共享相同的Cargo.lock,相同的output。[profiles]: 更改编译器的参数, overflow-checks, lto (link time optimizations), incremental增量编译。
2 project:动手猜数小游戏。 🔗
use rand::Rng;
use std::cmp::Ordering;
use std::io;
fn main() {
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;
}
}
}
}
String是标准库中定义的类型
String::new()表示new是String类型上实现的关联函数(associated function)。
注意点: 自动导入标准库中的包
io::stdin() 返回std::io::Stdin结构体。 read_line返回enum Result结构体,需要处理潜在的错误,可以使用expect。如果Result<T, E>类型的值是Ok成功,返回T。如果失败,程序在此处panic崩溃。
rand提供生成随机数的功能
use rand::Rng; 为什么需要导入Rng?
gen_range()方法在trait Rng中定义。如果要使用trait中的方法,trait必须导入到scope中。
如果不知道依赖的crate该怎么使用?cargo doc --open
3 和其他语言类似的概念 🔗
- 变量
- 可变性。 变量默认immutable不可变
- constants。声明时不用let用const。必须指明类型。const HOUR_IN_SECONDS: u32 = 60 * 60;
- shadowing。变量遮挡。
- 数据类型
- rust是静态类型语言,编译期需要知道类型
- 基本类型u32, i32, isize,usize, char。。。
- 复合类型tuple(fix size,diff type,anomy), array(fix size,same type),struct(),enum。。。
- 函数
- 注释
- 普通注释
- 文档注释
- 控制流, if else, for, while, loop, break, continue, goto。
4 所有权ownership 🔗
ownership, a feature that makes Rust different from other languages.
make memory safety guarantees without needing a garbage collector,
how Rust lays data out in memory.
stack & heap -> data on stack must fix-sized.
&str (on text seg) vs String (on heap, mutable).
String类型的变量是智能指针在stack上,包含3部分(ptr to heap addr, len, capacity)。
shallow copy: copying the data on the pointer, length, and capacity without copying the data on heap deep copy
let s1 = String::from("hello");
let s2 = s1; // 不仅仅是浅拷贝了s1, 而且invalid s1。 称为move。
let s1 = String::from("hello");
let s2 = s1.clone(); // deeply copy
println!("s1 = {}, s2 = {}", s1, s2);
trait Copy在栈上存储的数据类型实现了Copy trait, 需要move时执行的是copy。例如: 栈上的基本类型数组也实现了Copy trait
let x = 5;
let y = x; // Stack-Only Data: deep and shallow copying is same.
println!("x = {}, y = {}", x, y);
Rust won’t let us annotate a type with Copy if the type, or any of its parts, has implemented the Drop trait.
持有所有权的变量离开scope时,释放内存drop ,与C++的Resource Acquisition Is Initialization (RAII)类似。
ownership & function.
borrowing == reference
rust中所用的参数传递都是传值。 引用实现了copy trait。 按照copy语义,引用会复制一份交给调用的函数。
[char; 2] is Copy (as is any [T; N] where T: Copy).。
[string; 2]不能被自动copy。
borrow checker, . calculate overlap
ref &, deref *
slices == [T] -> DST, 单单切片类型[T]是不知道长度的,所以&[T]生成智能指针(ptr + len)就可以使用了
数组的切片,字符串的切片都是类似。 &[i32;9], &String, &str, &Vec
A slice is a kind of reference, let you reference a contiguous sequence of elements in a collection
let ch = "北京".chars().nth(0).unwrap();
println!("{ch}");
let chbyte = "北京".bytes().nth(0).unwrap();
println!("{chbyte}");
let s = String::from("hello world");
let hello = &s[0..5];
let world = &s[6..11];
let s = String::from("你好");
let ni = &s[0:2]; // error, 2不是[utf-8的字符边界]。
let string: String = String::from("hello");
println!("string addr = {:?}", string.as_ptr());
println!("string len = {:?}", string.len());
println!("string cap = {:?}", string.capacity());
// String == struct{ptr, len, cap} on stack.
println!("{:x?}", unsafe {
std::mem::transmute::<String, [u8; std::mem::size_of::<String>()]>(string)
});
// &str == struct{ptr, len}
let str = "hello";
println!("str addr = {:?}", str.as_ptr());
println!("str len = {:?}", str.len());
// println!("str cap = {:?}", str.capacity());
println!("{:x?}", unsafe {
std::mem::transmute::<&str, [u8; std::mem::size_of::<&str>()]>(str)
});
fn first_word(s: &String) -> &str {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[0..i];
}
}
&s[..]
}
string Literals are Slices
let s &str= "Hello, world!"; //&str is an immutable reference. a slice pointing to that specific point of the binary.
fn first_word(s: &String) -> &str {}
fn first_word(s: &str) -> &str {} // both &String values and &str values can call
5结构体struct 🔗
3类结构体。 struct A {}; struct B (); struct C;
tuple struct
struct Color(i32, i32, i32);
let black = Color(0, 0, 0);
struct AlwaysEqual;
let subject = AlwaysEqual; //unit-like structs. useful for implement a trait on some type, don’t have any data to store in the type
struct中的field拥有所有权时,field对象的生命周期 == struct对象的生命周期。
struct中的field中type是String, 或者type是&str。
当field是引用类型时,struct类型需要保证struct对象的生命周期 <= 依赖的引用类型对象的生命周期。所以需要引入lifetime标识符。
struct User<'a> {
active: bool,
username: &'a str,
email: &'a str,
sign_in_count: u64,
}
struct Rectangle {
width: u32,
height: u32,
}
let scale = 2;
let rect1 = Rectangle {
width: 30,
height: dbg!(scale * 10), // dbg! return the ownership of the expression’s value and print expression
};
println!("rect1 is {}", rect1); // error[E0277]: `Rectangle` doesn't implement `std::fmt::Display`
println!("rect1 is {:?}, {:#?}", rect1, rect1); //error[E0277]: `Rectangle` doesn't implement `Debug`
// solved: #[derive(Debug)]
dbg!(&rect1);
automatic referencing and dereferencing.
Rust automatically adds in &, &mut, or * to matches the signature of the method.
6枚举体enum 和 模式匹配 🔗
枚举类型的值范围是固定可枚举的。模式匹配。Option, match, if let。
enum Option<T> {
None,
Some(T),
}
模式匹配 match需要fill all arms, exhaustive patterns。如果只关心其中一个pattern,使用if let while let。
默认的匹配是绑定操作, 会转移所有权。
以引用方式去匹配, ref可以在匹配时获得一个变量的引用
7 Project, package,crate, module 🔗
如何组织rust项目的代码?
- Packages: A Cargo feature that lets you build, test, and share crates, Cargo.toml
- 一个package下可有多个bin crates, src/main.rs; 如果有src/bin有rs文件,each file will be a separate binary crate。
- 但最多一个lib crates, src/lib.rs。
- Crates: A tree of modules that produces a library or executable, src/main.rs or src/lib.rs
- Modules and use: Let you control the organization, scope, and privacy of paths +
- Paths: A way of naming an item, such as a struct, function, or module +
8 常见的集合 🔗
- vector 动态扩容数组
- String 字符的集合,字符串
- HashMap
Vec 🔗
// create
let v: Vec<i32> = Vec::new();
let v = vec![1, 2, 3];
// infer the type
// Updating a Vector
let mut v = Vec::new();
v.push(4);v.push(5);v.push(6);
// Reading Elements of Vectors
// via indexing or using the get method
let third: &i32 = &v[2];
let third: Option<&i32> = v.get(2);
match third {
Some(third) => println!("The third element is {third}"),
None => println!("There is no third element."),
}
// Iterating over the Values in a Vector
let v = vec![100, 32, 57];
for i in &v { // immutable reference to each element , type of i is &i32
println!("{i}");
}
let mut v = vec![100, 32, 57];
for i in &mut v { // mutable reference, type of i is &mut i32
*i += 50; // * dereference operator to get to the value in i
}
//Using an Enum to Store Multiple Types
enum SpreadsheetCell {
Int(i32),
Float(f64),
Text(String),
}
let row = vec![
SpreadsheetCell::Int(3),
SpreadsheetCell::Text(String::from("blue")),
SpreadsheetCell::Float(10.12),
];
// using trait object to store in vector
}
more details at “The Rustonomicon”。
UTF-8 encoding in String 🔗
在core包中只定义了str类型,以引用的形式出现&str。
在std包中定义了String类型,a growable, mutable, owned, UTF-8 encoded string type。
[]byte 。A String is a wrapper over a Vec
// create
let mut s = String::new();
let s = "initial contents".to_string(); // string literals implements the Display trait (define to_string)
let s = String::from("initial contents");
// Updating a String
let mut s = String::from("foo");
s.push_str("bar");
s.push('l');
// Concatenation with the + Operator or the format! Macro
let s1 = String::from("Hello, ");
let s2 = String::from("world!");
// fn add(self, s: &str) -> String ... coerce the &String argument into a &str
let s3 = s1 + &s2; // note s1 has been moved here and can no longer be used
let s = format!("{s2}{s3}");
// Indexing into Strings
// error....`String` cannot be indexed by `{integer}` UTF-8中有效字符不是一个字节,index at i 可能是乱码
let s1 = String::from("hello");
let h = s1[0];
// Slicing Strings
let hello = "Здравствуйте";
let s = &hello[0..4]; //s == "3"
let s = &hello[0..1]; //panic, `1` is not a `char boundary`; it is inside 'З' (bytes 0..2)
// Methods for Iterating Over Strings
for c in "Зд".chars() {
println!("{c}"); // З \n д \n
}
for b in "Зд".bytes() {
println!("{b}"); // 208 \n 151 \n 208 \n 180
}
HashMap<K, V> 🔗
//create
// not ofen use, so not in the prelude
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);
// standard lib less support, no built-in macro to construct them.
// Accessing Values in a Hash Map
let team_name = String::from("Blue");
let score = scores.get(&team_name).copied().unwrap_or(0);
for (key, value) in &scores {
println!("{}: {}", key, value);
}
// Hash Maps and Ownership
// Adding a Key and Value Only If a Key Isn’t Present
scores.entry(String::from("Yellow")).or_insert(50);
// Updating a Value Based on the Old Value
let text = "hello world wonderful world";
let mut map = HashMap::new();
for word in text.split_whitespace() {
let count = map.entry(word).or_insert(0); //or_insert method returns a mutable reference (&mut V) to the value
*count += 1;
}
//
其他数据集合 🔗
链表。
9 错误处理 🔗
recover from an error or to stop execution?
unrecoverable errors panic! 🔗
//RUST_BACKTRACE=1 cargo run
// RUST_BACKTRACE=full cargo run
fn main() {
let v = vec![1, 2, 3];
v[99];
}
recoverable errors Result<T, E> 🔗
enum Result<T, E> {
Ok(T),
Err(E),
}
use std::fs::File;
use std::io::ErrorKind;
let greeting_file_result = File::open("hello.txt");
let greeting_file = match greeting_file_result {
Ok(file) => file,
Err(error) => match error.kind() {
ErrorKind::NotFound => match File::create("hello.txt") {
Ok(fc) => fc,
Err(e) => panic!("Problem creating the file: {:?}", e),
},
other_error => {
panic!("Problem opening the file: {:?}", other_error);
}
},
};
use std::fs::File;
use std::io::ErrorKind;
fn main() {
let greeting_file = File::open("hello.txt").unwrap_or_else(|error| {
if error.kind() == ErrorKind::NotFound {
File::create("hello.txt").unwrap_or_else(|error| {
panic!("Problem creating the file: {:?}", error);
})
} else {
panic!("Problem opening the file: {:?}", error);
}
});
}
Shortcuts for Panic on Error: unwrap() and expect("infos")
Propagating Errors: ?
? after a Result value. From trait and from function.
? after Option value.
use std::fs::File;
fn main() {
let greeting_file = File::open("hello.txt")?;
}
use std::error::Error;
use std::fs::File;
fn main() -> Result<(), Box<dyn Error>> {
let greeting_file = File::open("hello.txt")?;
Ok(())
}
the ? operator in a function that must returns Result, Option, or another type that implements FromResidual
main的return type implement the std::process::Termination trait
10 泛型,trait,生命周期 🔗
生命周期。 即引用的有效作用域。
泛泛的数据类型 🔗
concrete types VS generic types -> reduce code duplication.
Monomorphization is the process of turning generic code into specific code by filling in the concrete types that are used when compiled.
// in function
fn largest<T: std::cmp::PartialOrd>(list: &[T]) -> &T {
let mut largest = &list[0];
for item in list {
if item > largest {
largest = item;
}
}
largest
}
// in struct
struct Point<T, U> {
x: T,
y: U,
}
// in enum
enum Option<T> {
Some(T),
None,
}
enum Result<T, E> {
Ok(T),
Err(E),
}
trait 定义通用、共享的行为 🔗
trait为类型定义通用、共享的行为。trait也可以限制泛型类型。
// 定义trait类型
trait A {
fn foo(&self);
// 可以有默认的函数实现
fn bar(&self) -> String {
String::from("bar")
}
}
// 在类型Type_B上实现trait A
impl A for Type_B {
fn foo(&self) {...};
}
// trait作为参数
fn C(item: &impl A) {
//accepts any type that implements the specified trait A
println!("hi, i have implement trait A, {}", item.bar());
}
// trait bound;
fn D<T: A>(item: T) {
println!("{}", item.bar());
}
// Returning Types that impl trait. must returning a single implement trait type
// especially useful in the context of closures and iterators,
//为实现了Display trait的类型实现ToString trait
impl<T: Display> ToString for T {
// --snip--
}
let s = 3.to_string(); //任何实现了Display的类型的对象可调用to_string方法。
检查引用(借用)有效性--生命周期 🔗
Borrow Checker
为了避免dangling references。
Lifetime Annotations in function Definitions
Lifetime Annotations in Struct Definitions
lifetime elision rules
如何计算每个变量的生命周期, a_ref的life time[4 - 9]-> a life time [code 2 - 10] (区间是包含关系) ✅
x的'a > y的'b, x = &y (❌)
x的'a <= y的'b, x = &y (yes)
let s: &'static str = "I have a static lifetime.";
fn main() {
let s1 = "hi";
let s2 = "world";
let largest = get_largest(s1, s2);
println!("largest: {}", largest);
}
fn get_largest<'a>(s1: &'a str, s2: &'a str) -> &'a str{
if s1.len() > s2.len() {
return s1
}
s2
}
&'static类型的值出现的两种方式
let a = "hello,world";
let b = Box::leak(Box::new(a));
Generic Type Parameters, Trait Bounds, and Lifetimes Together look a little complex, but all features happen at the compile time, no effect on runtime time.
11 测试 🔗
[type checking and borrow checking]不能检查程序的内部逻辑的正确性,所以引入testing。
rust testing中有好用的annotations和macros,以及如何组织单元测试,集成测试。
#[test]将函数变为测试函数。
$cargo test
running 1 test
test tests::it_works ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests adder
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
0 ignored -> #[should_ignore]
0 measured -> for benchmark tests
0 filtered out -> pass an argument to the cargo test command to run special tests
Doc-tests adder -> documentation tests.
assert_eq!, assert_ne! macros 用到了 operators == and !=, 所以参数需要实现PartialEq trait。 同时fail后,需要打印Debug format的信息,也需要实现Debug trait。基本类型已经实现了这2个trait。struct 和 enum类型需要自行实现,或者#[derive(PartialEq, Debug)]。
cargo test的一些参数选项可以控制单测的执行。
show the output of successful tests: cargo test -- --show-output.
Runn Single Tests: cargo test single_func_name
Run only the ignored tests: cargo test -- --ignored
如何组织测试代码?unit tests and integration tests
#[cfg(test)] 只会在cargo test下执行,避免拖慢编译速度。
tests/下 放集成测试文件,不需要#[cfg(test)]。
cargo test的测试执行顺序是 tests/ 集成测试 -> 单元测试 -> 文档测试。
12 project: io code 🔗
13 函数编程语言的特点: Iterator, closure 🔗
闭包 🔗
匿名函数,携带环境变量。
FnOnce (grand parent) -> FnMut (parent) -> Fn (child)
impl<T> Option<T> {
fn unwrap_or_else(&self, f: F) -> T
where F: FnOnce() -> T {
match self {
Some(x) => x,
None => f(), //all closures implement FnOnce
}
}
}
let a: Option<Vec<T>> = None;
a.unwrap_or_else(Vec::new);
#[derive[Debug]]
struct Rectangle {
width: u32,
height: u32,
}
let mut arr = [
Rectangle{width: 10, height: 1},
Rectangle{width: 3, height: 5},
Rectangle{width: 7, height: 12},
];
let mut num_sort_operations = 0;
arr.sort_by_key(|r| {
num_sort_operations += 1;
r.width}); //uses FnMut
迭代器 🔗
很多迭代器的函数参数都是闭包。
let v = vec![1, 2, 3];
let v_iter = v.iter(); //lazy and do nothing
for item in v_iter {
println!("Got: {item}");
}
v_iter.next(); //error
所有的迭代器实现了trait Iterator。
trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
...
}
let v1 = vec![1 , 2, 3];
// v1.into_iter(), v1.iter_mut()
let mut v1_iter = v1.iter(); // should mut, for item in v_iter take Ownership
assert_eq!(v1_iter.next(), Some(&1));
assert_eq!(v1_iter.next(), Some(&2));
assert_eq!(v1_iter.next(), Some(&3));
assert_eq!(v1_iter.next(), None);
// sum()
let total: i32 = v1_iter.sum(); // take ownership
// iterator adaptor map(), returns a new iterator
let v1: Vec<i32> = vec![1, 2, 3];
v1.iter().map(|x| x + 1); //lazy
// filter()
// collect()
let v2: Vec<_> = v1.iter().map(|x| x + 1).collect();
loop vs iterator
14 cargo & crates.io 🔗
Cargo.toml 🔗
[profile.dev]
opt-level = 0
[profile.release]
opt-level = 3
发布到crates.io 🔗
文档注释, cargo doc --open 查看本项目的文档注释对应的html文档。
/// # Examples
/// # Panics
/// # Safety
/// # Errors
// 文档测试
//!
Exporting a Convenient Public API with pub use
use my_crate::some_module::another_module::UsefulType; -> use my_crate::UsefulType;
在一个library A的src/lib.rs中pub use A::B::C::D; 可以在其他项目中使用use A::D导入到scope中。
在crates.io上创建账号并获取token。 cargo login token11222
更改toml文件中的cargo publish
workspace组织项目 🔗
一个workspace下管理多个文件夹,共享同一个Cargo.lock和output target目录
例如一个workspace下包含一个bin和2个libraries。workspace: add -> (bin: adder, lib: add_one, add_two)
$ mkdir add
$ cd add
// touch and edit Cargo.toml
[workspace]
members = ["adder"]
// under the add workspace directory
$ cargo new adder
Created binary (application) `adder` package
$ cargo build
现在workspace add目录下的结构:
├── Cargo.lock
├── Cargo.toml
├── adder
│ ├── Cargo.toml
│ └── src
│ └── main.rs
└── target
[workspace]
members = [
"adder",
"add_one",
]
// under the add workspace directory
$ cargo new add_one --lib
Created library `add_one` package
├── Cargo.lock ├── Cargo.toml ├── add_one │ ├── Cargo.toml │ └── src │ └── lib.rs ├── adder │ ├── Cargo.toml │ └── src │ └── main.rs └── target
edit add_one/src/lib.rs
// add_one/src/lib.rs
pub fn add_one(x: i32) -> i32 {
x + 1
}
在adder crate中增加对add_one lib crate的依赖。
Filename: adder/Cargo.toml
[dependencies]
add_one = { path = "../add_one" }
edit Filename: adder/src/main.rs
use add_one;
fn main() {
let num = 10;
println!("Hello, world! {num} plus one is {}!", add_one::add_one(num));
}
在workspace add目录下:
$ cargo run -p adder
Finished dev [unoptimized + debuginfo] target(s) in 0.0s
Running `target/debug/adder`
Hello, world! 10 plus one is 11!
由于workspace下的crate共享了Cargo.lock。all crates are using the same version of all dependencies
// add_one/Cargo.toml
[dependencies]
rand = "0.8.3"
如需要publish workspace下的crate到crate.io,需要publish -p 分开发布。
从crates.io上安装软件 🔗
cargo install 方面从crates.io上安装工具,并安装在$HOME/.cargo/
$ cargo install ripgrep
Updating crates.io index
Downloaded ripgrep v11.0.2
Downloaded 1 crate (243.3 KB) in 0.88s
Installing ripgrep v11.0.2
--snip--
Compiling ripgrep v11.0.2
Finished release [optimized + debuginfo] target(s) in 3m 10s
Installing ~/.cargo/bin/rg
Installed package `ripgrep v11.0.2` (executable `rg`)
$ rg --help
扩展cargo的子命令 🔗
如果在$PATh下有一个‘cargo-something’的exe,能够像cargo subcommand方式运行,‘cargo something’。
cargo --list
15 智能指针 🔗
通过struct实现,包含指针+meta信息,struct也实现Deref, Drop trait。 引用仅仅只是借用数据。指针指针可以own数据。
String, Vec
- Box
allocatie value of type T on heap,单所有权 - Rc
enables multiple ownership 多所有权, only immutable borrow - Ref
, RefMut , RefCell , 单所有权 the borrowing rules at runtime
let b = Box::new(5);
// implements the Deref trait, which allows Box<T> values to be treated like references
println!("b = {}", b);
println!("deref b = {}",*b); // *b == *(b.deref()) deref() 返回的是item的引用。
//必须使用Box才能定义这些类型。
enum List {
Cons(i32, List), // fail compile。 (1, (2, (3, Nil)))
Nil,
}
// 使用Box定义带有递归的类型(有一个字段的类型和自己有相同的类型)。
enum List {
Cons(i32, Box<List>),
Nil,
}
let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));
//因为smart pointer实现Deref trait,可以compiler做 dereference operator * 进而与普通引用一样使用
// deref coercion, &String -> &str,
fn hello(name: &str) {
println("hello: {name}");
}
let m = Box::new(String::from("Yellow"));
//&Box<String> --Box impl Defref--> &String --String impl Defref--> &str --> type matching.
// or manually, &(*m)[..]
hello(&m);
deref coercion
- from &T to &U when T: Deref<Target=U>
- from &mut T to &mut U when T: DefrefMut<Target=U>
- from &mut T to &U when T: Defref<Target=U>
std::mem::drop在prelude里, drop(x) 可提前释放内存。
Rc
let a = Cons(5, Box::new(Cons(10, Box::new(Nil))));
let b = Cons(3, Box::new(a)); // Box::new() take ownership
let c = Cons(4, Box::new(a)); // failed, use of moved value: `a`
instead
use std::rc::Rc; //not in the prelude
enum List {
Cons(i32, Rc<List>),
Nil,
}
let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
let b = Cons(3, Rc::clone(&a));
let c = Cons(4, Rc::clone(&a));
println!("after clone, rc count = {}", Rc::strong_count(&a));
// immutable
//Drop trait decreases the reference count on Rc<T> value
RefCell
mutate data even when there are immutable references to that data, use unsafe code inside
在编译期间检查是保守的,可能会reject一些正确的程序。RefCell类型的变量在runtime时进行借用检查,失败会panic。
interior mutability: an immutable type exposes an API for mutating an interior value.
#[derive(Debug)]
enum List {
Cons(Rc<RefCell<i32>>, Rc<List>),
Nil,
}
use crate::List::{Cons, Nil};
use std::cell::RefCell;
use std::rc::Rc;
fn main() {
let value = Rc::new(RefCell::new(5));
let a = Rc::new(Cons(Rc::clone(&value), Rc::new(Nil)));
let b = Cons(Rc::new(RefCell::new(3)), Rc::clone(&a));
let c = Cons(Rc::new(RefCell::new(4)), Rc::clone(&a));
*value.borrow_mut() += 10;
println!("a after = {:?}", a);
println!("b after = {:?}", b);
println!("c after = {:?}", c);
// a after = Cons(RefCell { value: 15 }, Nil)
// b after = Cons(RefCell { value: 3 }, Cons(RefCell { value: 15 }, Nil))
// c after = Cons(RefCell { value: 4 }, Cons(RefCell { value: 15 }, Nil))
}
reference cycles -> leak memory
use crate::List::{Cons, Nil};
use std::cell::RefCell;
use std::rc::Rc;
#[derive(Debug)]
enum List {
Cons(i32, RefCell<Rc<List>>),
Nil,
}
impl List {
fn tail(&self) -> Option<&RefCell<Rc<List>>> {
match self {
Cons(_, item) => Some(item),
Nil => None,
}
}
}
fn main() {
let a = Rc::new(Cons(5, RefCell::new(Rc::new(Nil))));
println!("a initial rc count = {}", Rc::strong_count(&a));
println!("a next item = {:?}", a.tail());
let b = Rc::new(Cons(10, RefCell::new(Rc::clone(&a))));
println!("a rc count after b creation = {}", Rc::strong_count(&a));
println!("b initial rc count = {}", Rc::strong_count(&b));
println!("b next item = {:?}", b.tail());
if let Some(link) = a.tail() {
*link.borrow_mut() = Rc::clone(&b);
}
println!("b rc count after changing a = {}", Rc::strong_count(&b));
println!("a rc count after changing a = {}", Rc::strong_count(&a));
// Uncomment the next line to see that we have a cycle;
// it will overflow the stack
// println!("a next item = {:?}", a.tail());
}
weak reference, Rc::downgrade, Weak
use std::cell::RefCell;
use std::rc::Rc;
#[derive(Debug)]
struct Node { // 树上节点的数据结构
value: i32,
children: RefCell<Vec<Rc<Node>>>,
}
fn main() {
let leaf = Rc::new(Node {
value: 3,
children: RefCell::new(vec![]),
});
let branch = Rc::new(Node {
value: 5,
children: RefCell::new(vec![Rc::clone(&leaf)]),
});
}
use std::cell::RefCell;
use std::rc::{Rc, Weak};
#[derive(Debug)]
struct Node {
value: i32,
parent: RefCell<Weak<Node>>,
children: RefCell<Vec<Rc<Node>>>,
}
use std::cell::RefCell;
use std::rc::{Rc, Weak};
#[derive(Debug)]
struct Node {
value: i32,
parent: RefCell<Weak<Node>>,
children: RefCell<Vec<Rc<Node>>>,
}
fn main() {
let leaf = Rc::new(Node {
value: 3,
parent: RefCell::new(Weak::new()),
children: RefCell::new(vec![]),
});
// leaf parent = None
println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());
let branch = Rc::new(Node {
value: 5,
parent: RefCell::new(Weak::new()),
children: RefCell::new(vec![Rc::clone(&leaf)]),
});
*leaf.parent.borrow_mut() = Rc::downgrade(&branch);
//leaf parent = Some(Node { value: 5, parent: RefCell { value: (Weak) },
//children: RefCell { value: [Node { value: 3, parent: RefCell { value: (Weak) },
//children: RefCell { value: [] } }] } })
println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());
}
use std::cell::RefCell;
use std::rc::{Rc, Weak};
#[derive(Debug)]
struct Node {
value: i32,
parent: RefCell<Weak<Node>>,
children: RefCell<Vec<Rc<Node>>>,
}
fn main() {
let leaf = Rc::new(Node {
value: 3,
parent: RefCell::new(Weak::new()),
children: RefCell::new(vec![]),
});
println!(
"leaf strong = {}, weak = {}",
Rc::strong_count(&leaf),
Rc::weak_count(&leaf),
); //leaf strong = 1, weak = 0
{
let branch = Rc::new(Node {
value: 5,
parent: RefCell::new(Weak::new()),
children: RefCell::new(vec![Rc::clone(&leaf)]),
});
*leaf.parent.borrow_mut() = Rc::downgrade(&branch);
println!(
"branch strong = {}, weak = {}",
Rc::strong_count(&branch),
Rc::weak_count(&branch),
); //branch strong = 1, weak = 1
println!(
"leaf strong = {}, weak = {}",
Rc::strong_count(&leaf),
Rc::weak_count(&leaf),
); // leaf strong = 2, weak = 0
}
//leaf parent = None
println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());
println!(
"leaf strong = {}, weak = {}",
Rc::strong_count(&leaf),
Rc::weak_count(&leaf),
); //leaf strong = 1, weak = 0
}
16 并发 🔗
无畏并发: 并发问题在编译期间暴露,而为运行时出现后再去花时间去复现,因此让用户无畏惧并发。
创建线程 🔗
use std::thread;
use std::time::Duration;
fn main() {
thread::spawn(||{
println!("i am a thread");
thread::sleep(Duration::from_millis(1);
})
thread::sleep(Duration::from_millis(2));
}
use std::thread;
use std::time::Duration;
fn main() {
let v = vec![1, 2, 3];
let handle = thread::spawn(move || {
println!("Here's a vector: {:?}", v);
});
handle.join().unwrap();
}
线程间的通信,message-passing concurrency,via channel 🔗
use std::{
sync::mpsc::{self, Sender},
thread,
time::Duration,
};
fn test_channel() {
let (tx, rx) = mpsc::channel();
// let tx1 = tx.clone();
let tx1 = Sender::clone(&tx);
thread::spawn(move || {
let vals = vec![
String::from("hi"),
String::from("from"),
String::from("the"),
String::from("thread"),
];
for val in vals {
tx1.send(val).unwrap();
thread::sleep(Duration::from_secs(1));
}
});
thread::spawn(move || {
let vals = vec![
String::from("more"),
String::from("messages"),
String::from("for"),
String::from("you"),
];
for val in vals {
tx.send(val).unwrap();
thread::sleep(Duration::from_secs(1));
}
});
for received in rx {
println!("got: {}", received);
}
}
线程间共享内存(共享状态), shared-state concurrency 🔗
已经可以通过channel来通信,另外一种方式是通过共享内存来通信。
channel的方式类似于单一所有权,数据被发送给channel后,所有权就转移到channel上
共享内存的方式类似多个所有权,多个线程可以访问。
Mutex
use std::sync::Mutex;
let m = Mutex::new(5);
{
let mut num = m.lock().unwrap();
*num = 6;
}
println!("m = {:?}", m); //m = Mutex { data: 6, poisoned: false, .. }
use std::sync::{Mutex, Arc};
use std::sync::thread;
fn main() {
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..10 {
let counter = Arc::clone(&counter);
let handle = thread::spawn(||{
let mut num = counter.lock().unwrap(); //Mutex<T> provides interior mutability
*num += 1;
})
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("counter:{}", *counter.lock().unwrap());
}
Sync, Send trait 🔗
std::marker::Send trait
实现了Send的类型的值的所有权可以在线程中转移。
大多rust中的类型都是Send的,也有例外,Rc
std::marker::Sync
标准库中提供的类型。
把rustc对并发安全的保证扩展到用户自定义的类型上。
17 rust在面向对象编程上的特点 🔗
18 模式和匹配 patterns and matching 🔗
19 advance features 🔗
- Unsafe Rust: static analysis is conservative.
- Advanced traits:
- Advanced types
- Advanced functions and closures: function pointers and returning closures
- Macros:
unsafe rust 🔗
- 解引用裸指针 *const T and *mut T
- 调用不安全的方法
- todo
advance trait 🔗
trait是用到定义共享的行为的。
#![allow(unused)]
fn main() {
trait Add<Rhs=Self> {
type Output;
fn add(self, rhs: Rhs) -> Self::Output;
}
}
advance type 🔗
类型系统有两个基本的保障: 需要知道一个类型的所有值占多少空间。一个类型下的所有值占用相同的空间。
str, dyn trait, slice 都是DST。不能直接使用这种类型。如果允许,let s1 str = "aa"; let s2 str = "bbb";
那么str类型下的s1和s2占不同的空间,违背类型系统的保障。所有DST类型在语言中都是以引用的形式(智能指针)出现。
&str -> the address of the str and its length.
str -> &str, Box
Macros 🔗
20 project: web server 🔗
thread, Arc, Mutex, channel...
其他附录 🔗
源码结构 🔗
项目组织 🔗
引入第三方依赖crate 🔗
例如在toml文件中引入rand crate后,cargo build的输出,Cargo.lock的内容。
module 🔗
package, crate, module。
模块机制: rust的mod即是命名空间。
use 是optional,可以使用full path 替换,不是必须使用的。
use作用:将其他module的item导入到current namespace,方便访问。 不带{}的mod作用:将module的内容插入到当前file中。
声明:mod 与 pub mod
引入:use 与 pub use
extern crate rand;
use rand::Rng; // in order to use gen_range method in trait `Rng`.
fn main() {
// gen_range is a method in Rng trait.
// the basic idea is this: methods are defined on something called `trait`,
// and for the method to work, it needs the trait to be in scope.
let guess = rand::thread_rng().gen_range(1, 101);
println!("guess={}", guess);
}
工作空间 rust workspace 🔗
数据 🔗
堆和栈 🔗
let s = "hello".to_string();
println!("addr={:p}", &s);
// 输出的地址是 栈中s = ptr|len|cap这个结构体的地址,还是ptr中堆上的地址?
let a = "hello"; // a is fatten pointer
let b = a; // copy trait, b is also a fatten pointer
println!("&a={:p}, &b={:p}", &a, &b); // different addr on stack
println!("*a={:p}, *b={:p}", &*a, &*b); // same addr which is on BSS segment
static Data_Seg: u32 = 1;
fn foo () {}
fn main() {
let hello = "hello".to_string();
let data = Box::new(1);
println!("RODATA = {:p}", "hello world");
println!("Data static var = {:p}", &Data_seg);
println!("Text seg = {:p}", foo as *const ());
// String的结构体分配在栈上,其引用指向栈地址
println!("STACK (&hello) = {:p}", &hello);
// hello持有
// 通过解引用获取堆上数据,然后取其引用
println!("HEAP (&*hello) = {:p}", &*hello);
// Box实现了Pointer trait, 无需额外的解引用
println!("HEAP box impl pointer, {:p}, {:p}", data, &*data);
}
错误处理 🔗
把错误封装在Result<T, E>中。 错误传播 ?。 unwrap()方法只关心成功的返回结果T,若出错,整个程序终止。
迭代器 🔗
for循环可以用于任何实现了IntoIterator trait的数据结构。
代码 🔗
函数与闭包 🔗
接口与虚表 🔗
虚表是每个trait有一份,还是每个对象有一份,还是每个胖指针有一份?
godbolt.org
impl TraitA for TypeB {}。 虚表是每个<Trait, Type>有一份,在编译时生成好在二进制文件里。
let s1 = String::from("hello");
let s2 = String::from("byebye");
let traitObj1: &dyn Display = &s1;
let traitObj2: &dyn Debug = &s1;
let traitObj3: &dyn Display = &s2;
let traitObj4: &dyn Debug = &s2;
let (addr1, vtable1) = unsafe{ transmute::<_, (usize, usize)>(traitObj1 as *const dyn Display) };
let (addr2, vtable2) = unsafe{ transmute::<_, (usize, usize)>(traitObj2 as *const dyn Debug) };
let (addr3, vtable3) = unsafe{ transmute::<_, (usize, usize)>(traitObj3 as *const dyn Display) };
let (addr4, vtable4) = unsafe{ transmute::<_, (usize, usize)>(traitObj4 as *const dyn Debug) };
println!("s1:{:p}, s2: {:p}, main(): {:p}", &s1, &s2, main as *const());
println!("addr1: 0x{:x}, vtable1: 0x{:x}", addr1, vtable1);
//
assert_eq!(addr1, addr2);
assert_eq!(addr3, addr4);
// String + Display
assert_eq!(vtable1, vtable3);
// String + Debug
assert_eq!(vtable2, vtable4);
宏 🔗
- 声明宏
- 过程宏(函数宏 派生宏 属性宏)
expression 🆚 statement 🔗
rust只有两种语句(无返回值),其他的均是表达式(有返回值)。 一种语句是表达式语句。在表达式后加;。一种语句是声明式语句。
let x = (ley y = 5); //error
let mut y = 5;
// y = 6 is expression, return ().
let x = ( y = 6); // x has the value `()`, not 6
- 所有权与借用, 生命周期
- ownership, borrowing
fn main() {
{
let s1 = String::from("hallo");
let s2 = s1;
println!("{},{}", s1, s2); // error[E0382]: borrow of moved value: `s1`.
}
{
let s1 = String::from("hallo");
let s2 = s1.clone(); //
println!("{}, {}", s1, s2);
}
{
//reference
let s1 = String::from("hallo"); // s1 is the owner of data 'hallo'.
let s2 = &s1; // s2 is a reference which points to the same data.
println!("s1={}, s2={}", s1, s2);
}
{
let s1 = String::from("hallo");
let s2 = &s1;
let s3 = &s2; // two readonly reference of 'hallo'
println!("s1={}, s2={}", s1, s2);
println!("s3={}, len={}", s3, s3.len());
}
{
//change string
let mut s1 = String::from("hello"); // mutable borrow
let s2 = &mut s1; // mutable borrow occurs
s2.push('~');
println!("s2 = {}", s2); // s2 = hello~
// can only use the original when the borrowed item is dropped.
// s2 is dropped, so s1 can use.
println!("s1 = {}", s1); // s1 = hello~
}
{
let mut s1 = String::from("hello"); // mutable borrow
let s2 = &mut s1; // mutable borrow occurs
s2.push('~');
println!("s2 = {}", s2);
println!("s1 = {}", s1); // not compile!
s2.push('~')
}
{
let mut s1 = String::from("hello"); // mutable borrow
let s2 = &mut s1; // mutable borrow occurs
s2.push('~');
println!("s1 = {}", s1); // not compile!
println!("s2 = {}", s2);
}
{
let mut words = vec![String::from("hallo"), String::from("español"), String::from("brett")];
println!("{:?}", words);
let t = words[1].clone();
words[1] = words[2].clone();
words[2] = t;
println!("{:?}", words);
// can't take two mutable loans from one vector,
// just cast them to raw pointers to do the swap.
words.swap(1, 2); // built-in swap function.
println!("{:?}", words);
}
/*
std::mem::swap(..) // use unsafe
pub fn swap(&mut self, a: usize, b: usize) {
unsafe {
let pa: *mut T = &mut self[a];
let pb: *mut T = &mut self[b];
ptr::swap(pa, pb);
}
}
*/
}
智能指针 🔗
Deref trait,
pub trait Deref {
type Target: ?Sized;
fn deref(&self) -> &Self::Target;
}
Drop trait
动态分派,静态分派 🔗
静态分派: 靠泛型的单态化实现,编译时确定函数调用。 callee确定,可以inline。 动态分派:靠trait object实现,运行时确定函数调用。
trait类型是DST类型, 在编译时无法确定size。
trait Fly {
fn fly(&self);
}
// Fly是trait类型,在编译时无法确定size,无法直接作为param 和return。
// 和java,golang的interface有区别
fn test(arg: Fly){}
// 利用泛型约束T, ok
fn test<T: Fly>(arg: T) {}
trait类型是DST, 实现trait的具体类型可能有多个,指向trait类型的指针(胖)需要记录
use std::raw // module
pub struct TraitObject {
pub data: *mut(), // 实现trait的具体对象,对象里不包含指向虚函数table的指针。与C++不同。
pub vtable: *mut(), //
}
use std::mem;
struct Bird;
struct Duck;
impl Fly for Bird {
fn fly(&self) {println!("bird can fly");}
}
impl Fly for Duck {
fn fly(&self) {println!("duck can fly");}
}
// &Fly, Box<Fly> both trait object
fn print_trait_object(p: &Fly) {
let (data, vtalbe): (usize, usize) = unsafe{mem::transmute(p)};
println!("trait object: data={}, vtable={}", data, vtable);
unsafe{
let v: *const usize = vtable as * const() as * const usize;
println!("vtable {}, {}, {}, {}", *v, *v.offset(1), *v.offset(2), *v.offset(3));
}
}
fn main() {
let duck Duck;
let p_duck = &duck;
let p_fly = p_duck as &Fly;
/*
let p_fly = TraitObject{
data: &p_duck, // store data
vtable: &Fly_for_Duck_vtable, //store method
}
*/
p_fly.fly();
/*
(p_fly.vtable.method)(p_fly.data);
*/
println!("size of p_duck={}, size of p_fly={}", mem::size_of_val(p_duck), mem::size_of_val(p_fly));
let duck_fly: usize = unsafe{mem::transmute(Duck::fly)};
let bird_fly: usize = unsafe{mem::transmute(Bird::fly)};
println!("Duck::fly={}, Bird::fly={}", duck_fly, bird_fly);
print_trait_object(p_fly);
let bird Bird;
print_trait_object(&brid as &Fly);
}x
object safe: trait object受到限制。
- 当trait上有Self:sized限制时,无法构造trait obj。
- trait里所有函数都是object safe
- 当trait里函数上有Self: sized限制时,该函数不会加入到vtable中。
- 当函数中除了self参数之外还有Self类型作为参数或者返回类型时,
- 当函数有泛型参数时。
closure 🔗
如果一门编程语言,其函数是一等公民,那么它必然会支持闭包(closure),因为函数作为返回值往往需要返回一个闭包。
闭包相当于一个捕获变量的结构体,实现了 FnOnce 或 FnMut 或 Fn。
fn f<F: FnOnce() -> String>(g: F) {
println!("{}", g());
}
let mut s = String::from("halo");
let t = String::from("world");
f(||{
s += &t;
s
});
struct Closure<'a>{
s: String,
t: &'a String,
}
impl <'a> FnOnce<()> for Closure<'a> {
type Output = String;
fn call_once(self) -> Output{
self.s += &*self.t;
self.s
}
}
f(Closure{s: s, t: &t});
标准库中: Rc, Box::leak() 突破单一所有权
Iterator 不断构造新结构,支持lazy evaluation
Rc::clone 内部可变性。
tree src, readme.md 目录框架
dors.rs <-> code 方便来回切换
从trait开始: docs上required method(需要被实现的方法), provided method, implementations on foreign types, implementor。
从struct开始: methods, trait implementation, auto trait, blanket trait
[Blanket Implementations}(https://doc.rust-lang.org/nightly/src/core/any.rs.html#200)
#[stable(feature = "rust1", since = "1.0.0")]
impl<T: 'static + ?Sized> Any for T {
fn type_id(&self) -> TypeId {
TypeId::of::<T>()
}
}
T: 'static -> the type does not contain any non-static references。
T类型是要么拥有所有权,要么是拥有静态引用&str。
blanket trait例如Any, 为泛型T实现了Any trait, 那么任何类型只要满足bound trait
match is designed to work quite well without taking ownership
fn tree_weight_v2(t: &BinaryTree) -> i32 {
// ^~~~~~~~~~~ The `&` means we are *borrowing* the tree
// *t == L-value expression
match *t { // *t == not making a copy of the tree, nor moving it to a new temporary location
BinaryTree::Leaf(payload) => payload,
BinaryTree::Node(ref left, payload, ref right) => {
tree_weight_v2(left) + payload + tree_weight_v2(right)
}
}
}
// 当所有权转移时,数据的可变性可能发生改变。
let immutable_box = Box::new(5u32);
println!("immutable_box contains {}", immutable_box);
//*immutable_box = 4;
// 移动box,改变所有权(和可变性)
let mut mutable_box = immutable_box;
println!("mutable_box contains {}", mutable_box);
*mutable_box = 4;
println!("mutable_box now contains {}", mutable_box);
}
// 部分移动
#[derive(Debug)]
struct Person {
name: String,
age: u8,
}
let person = Person {
name: String::from("brett"),
age: 19,
};
// `name` 从 person 中移走,但 `age` 只是引用
let Person { name, ref age } = person;
println!("The person's age is {}", age);
println!("The person's name is {}", name);
// 报错!部分移动值的借用:`person` 部分借用产生
//println!("The person struct is {:?}", person);
// `person` 不能使用,但 `person.age` 因为没有被移动而可以继续使用
println!("The person's age from person struct is {}", person.age);
直接访问Vec结构体中的len字段,可以吗? 分两种情况吧,safe rust不行。 unsafe rust下 mem::transmute 应该是可以的。
error[E0616]: field len of struct Vec is private
https://doc.rust-lang.org/beta/error_codes/E0616.html
"a".to_string() vs "a".to_owned()
https://users.rust-lang.org/t/to-string-vs-to-owned-for-string-literals/1441
How to step into std source code when debugging in VS Code?`
solved
Source Path Remapping
格式化输出 🔗
{} 适用于 实现了 std::fmt::Display类型。 {:?} 适用于 实现了std::fmt::Debug类型
rust书写的习惯 🔗
类型是驼峰命名。 变量名和函数名是蛇形命名。
参考与资料 🔗
[1] welcome to rust 101
[2] rust by example
[3] rust blog
[4] this week in rust
[5] inside rust
[6] Tour of Rust 以简单的例子串讲,很好入手
[7]《The Rust Programming Language》 done
[8]《Rust Book experiment: Experiment Introduction》
[9] rust noniom
[10] Rust Language Cheat Sheet 适合快速浏览,有问题时查看的工具书 done
[11] rust by example 每篇都很短并配有例子,很好入手。done
[12] google comprehensive-rust 4days
[13] awesome block chain rust
[14]
[15]
[16]
[17]
[18]
[19]
[20]
text editor. https://www.philippflenker.com/hecto
pngme book. build your text editor with rust.
writing an os in rust. os.phil-opp.com/zh-CN/ github.com/phil-opp/blog-os