属于名词解释性的文章,知道就是知道,知道是设计者是这么做得而已。。。balabala。不属于原理性的知识。
阅读rust-lang时遇到的问题 🔗
原因是git大仓下有些submodule, 这些submodule在clone时并不会随大仓一起下载到本地。
本地调试 rustc -g main.rs, lldb main ,b main, run, step
单步调试找不到源代码? 🔗
遇到调用库函数时进入到汇编, 未跳到源代码的位置。
//settings.json
{
"lldb.executable": "rust-lldb",
"lldb.launch.sourceLanguages": ["rust"],
"lldb.launch.sourceMap": {
"/rustc/e75aab045fc476f176a58c408f6b06f0e275c6e1/":
"/Users/brett/.rustup/toolchains/nightly-aarch64-apple-darwin/lib/rustlib/src/rust"
},
}
增加配置后,rust/library里的core, std, alloc都可以step in进去了。 但是compile相关的代码还是step in到了汇编。

复制后, lldb debug step into compiler了。

main方法的return类型 🔗
1 | fn main() -> Option<Result<String, i32>> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ `main` can only return types that implement `Termination`
|
= help: consider using `()`, or a `Result`
main函数的return type必须实现std::process::Termination特征
,
impl Termination for Infallible
impl Termination for !
impl Termination for ()
impl Termination for ExitCode
impl<T: Termination, E: Debug> Termination for Result<T, E>
语言中的sugar 🔗
coercion 🔗
弱化类型去匹配方法的签名。 &mut T to &T。
.操作符的背后(编译器)做了很多操作
auto-referencing, auto-dereferencing, and coercion until types match。
编译器的method lookup
Method Resolution: Derefs or borrow x until x.f() works.
有两种coercion: auto_ref, auto_deref
- auto_ref, T -> &T, mut T -> &mut T (单纯的想要引用T)
- object.something(), rustc 可以添加&object, &mut object, *object来是caller的类型与something的签名匹配。
- auto_deref, 自动调用Deref::deref (Defref描述了外部类型转为内部类型,一般用于智能指针)
- Box
实现了Deref。 &*x 相当于 "reborrow"
- Box
let a: Box<String> = Box::new("hello".to_owned());
let b: &String = &*a;
let c: &String = &(*(std::ops::Deref::deref(&a)));
let a = &Box::new(2);
test(&**a); // one * to dereference the Box, get &i32
// one * to dereference the &, get i32
// reborrow with &, get &i32
fn test(a: &i32) { println!("{}", *a) }
#[derive(Debug)]
struct MyI32(i32);
impl MyI32 {
fn print_string(&self) {
println!("&self={:?}", self); // auto add *
println!("*self={:?}", *self);
}
}
#[test]
fn test_my_i32() {
let mi32 = MyI32(5);
mi32.print_string();
let box_my_i32 = Box::new(mi32);
box_my_i32.print_string(); // Box<MyI32> -> &MyI32
(&box_my_i32).print_string();
(*box_my_i32).print_string();
// 多了一个& ,compile自动去掉来match类型
(&*box_my_i32).print_string();
}
Borrow ↔️ AsRef 🔗
很相近,但不同
&借用(引用)是blanket implement,所有类型都实现了Borrow
AsRef是引用到引用的转换。 定制化 as_ref 后的类型, 可能self不同的另外一种(String -> &str), 可能是self结构下的某一个字段的引用。
- 对值类型的转换
From<T>,Into<T> - 对引用类型的转换
AsRef<T>,AsMut<T>
Borrow在RefCell上有实现, 同时Borrow有兜底实现impl<T> Borrow<T> for T 得到&T。
AsRef也有兜底实现。
trait的关联类型 🔗
为什么需要关联类型?使用上不方便的地方。
ref 语法问题 🔗
ref pattern
let a = 'A';
let ref ref_a1 = a;
let ref_a2 = &a; // the same effect
let x = 2;
let ref ref_x1 = x;
let ref_x2 = &x; // ref_x1, ref_x2 same
'static 🔗
let a: &'static str = "hello";
// Trait bound
// T type does not contain any non-static references
fn generic<T: 'static + impl Debug>(x: T) {
println!("{:?}", x);
}
let i = 5;
genric(i); //✅
genric(&i) //❌
多重引用 🔗
let vec1 = vec![1, 2, 3];
// 对 vec1 的 `iter()` 举出 `&i32`,
// `find` 方法会把迭代器元素的引用传给闭包
// 传给闭包的类型是&&i32 用&&x解构, x 类型为i32。
println!("find 2 in vec1: {:?}", vec1.iter().find(|&&x| x == 2));
T as _ ? 🔗
AtomicPtr<()>, *mut T as _
Even_vtable, Odd_vtable 为什么要区分?
智能指针 🔗
在rust中trait决定了类型的行为, 智能指针的行为涉及到Deref trait , Drop trait, 拥有指针语义,拥有内存自动管理。
pub trait Deref {
type Target: ?size;
/// Dereference the value
/// *X =》 *(X.deref())
fn deref(&self) -> &Self::Target;
}
Box
Vec<T>, String
Rc, Arc
struct RcBox<T: ?Sized> {
strong: Cell<usize>,
weak: Cell<usize>,
value: T,
}
struct ArcInner<T: ?Sized> {
strong: atomic::AtmoicUsize,
weak: atmoic::AtomicUsize,
data: T,
}
Cell, RefCell 内部可变性
Mutex, RwLock
Rc<RefCell<T>> 一个RefCellRefCell<Rc<T>>
Cell, RefCell在功能上没有区别。
Cell
RefCell
Rc --downgrade() --> Weak
Weak -- Upgrade() --> Option
Box
Pin, Unpin 🔗
很多情况下,其实Pin是用不到的。因为大多数情形下,都实现Unpin Traits。
在引入的async/await编程中, Futrue的移动, 如果结构体包含了自引用, 当对象发生移动时引用的指针会失效,所以需要Pin住
Pin对象的手段: 禁止获取&mut T.
Pin<P>, 包裹的p(必须是实现Deref trait的对象)是指针指向原始类型T, 理解为 Pin<P<T>>
给定 Pin<P<T>>类型的数据,只要 T 不满足 Unpin trait,则 Safe Rust 下无法获得 &mut T和 T,从而让 T 不能被 move
struct Example {
data: String,
ptr: NonNull<String>,
}
impl<P: Deref<Target: Unpin>> Pin<P> {
// ...
pub const fn new(pointer: P) -> Pin<P> {
// ...
}
// ...
}
Send, Sync 🔗
impl<T> Send for Arc<T>
where
T: Sync + Send + ?Sized,
impl<T> Sync for Arc<T>
where
T: Sync + Send + ?Sized,
impl<T: ?Sized + Send> Send for RwLock<T>
impl<T: ?Sized + Send + Sync> Sync for RwLock<T>
impl<T: ?Sized + Send> Send for Mutex<T>
impl<T: ?Sized + Send> Sync for Mutex<T>
impl<T: Send> Send for Sender<T>
impl<T> !Sync for Sender<T>
// T: Send + Sync, 才为Arc实现Send。所以Arc<RefCell> 不能在线程spawn时使用。
impl<T> Send for Arc<T> where T: Sync + Send + ?Sized
impl<T> Sync for Arc<T> where T: Sync + Send + ?Sized
Arc::new(RwLock::new(mpsc::Sender
Sender
Sender
- Send + Sync -> 大多数类型都是的
- !Send + Sync ->
- !Send + ! Sync -> Rc, Raw pointer, Arc
where T: Send + sync - Send + !Sync -> 内部可变的UnsafeCell, mpsc::Sender, Cell, RefCell
trait object DST 🔗
let mut buf: Vec<u8> = vec![];
let writer: &mut std::io::Write = &mut buf;
trait object( data vptr)
. .
. .
. .
. .
buf(buffer, capacity, lenght) vtable impl Write for Vec<u8>
析构函数,size, align
write() ->
flush() ->
c++是将vptr和对象保存在一起。给基础类型int,string扩展方法就做不到了。 NonNull
类型占用了多少内存? &dyn Trait -> (*mut data, *mut vtable)
trait Oper {
fn add(&self) -> i32;
fn sub(&self) -> i32;
}
#[repr(C)]
struct FatPointer<'a> {
data: &'a mut Data,
vtable: *const usize,
}
struct Data {
a: i32,
b: i32,
}
fn add(s: &Data) -> i32 {
s.a + s.b
}
fn sub(s: &Data) -> i32 {
s.a - s.b
}
fn main() {
let mut data = Data{a: 3, b: 2};
let vtable = vec![
0, // 指向drop
5, // vtable的长度
8, // 内存对齐的size
add as usize,
sub as usize,
];
let fat_pointer = FataPointer{data: &mut data, vtable: vtable.as_ptr()};
let dyn_ref = unsafe { std::mem::transmute::<FatPointer, &dyn Oper>(fat_pointer)};
println!("add: {}", dyn_ref.add());
println!("sub: {}", dyn_ref.sub());
}
Sized ?Sized 🔗
对象安全, trait object 🔗
trait object类型擦除, (data, vatable(funcname -> func pointer))
- trait的所有父trait都是对象安全的
- 不能以Sized为父trait
- 所有关联函数能从trait obj进行分发
- 不带任何类型参数。shape of argument 不能带泛型。
- 方法除了接收方(第一个参数)之外,其他地方不能使用Self类型。trait object类型擦除,无法返回与trait无关的具体类型。
- 接收方是引用或者指针的形式。&Self, &mut self, Box
- 没有where Self:Sized
trait中的方法仅仅依赖于trait中的定义。
错误处理 🔗
- ?
- Result::Ok(x), 返回x
- Result::Err(e), return Err(e)
- Option::Some(x), 返回x
- Option::None, return None
- unwrap()
- Result::Ok(x), 返回x
- Result::Err(e), panic
- Option::Some(x), 返回x
- Option::None, panic
fn multiply(first_number_str: &str, second_number_str: &str) -> Result<i32, ParseIntError> {
first_number_str.parse::<i32>().and_then(|first_number| {
second_number_str.parse::<i32>().map(|second_number| first_number * second_number)
})
}
do_something_may_fail()?;
// equals
match do_something_may_fail() {
Ok(v) => v,
Err(e) => return Err(e),
}
// catch panic
let result = std::panic::catch_unwind(|| {
panic!("oh no");
});
lifetime 🔗
函数返回引用时,除非是静态引用,那么这个引用一定和带有引用的某个输入参数有关。
要建立正确的输入和返回值之间的关系,这个关系和函数内部的实现无关,只和函数的签名有关。
unsafe 🔗
标准库中unsafe。
Slice::get_unchecked() 执行未经检查的索引,允许随意地违反内存安全
mem::transmute将一些值重新解释为具有给定的类型,以任意的方式绕过类型安全
指向一个 Sized 类型的原始指针都有一个offset方法
FFI(Foreign Function Interface)函数的调用都是不安全的
对原始指针进行解引用。 例如链表实现中使用node_ptr: NonNull<Node<T>> ,node_ptr.as_ptr()获得Node的可变引用。
NonNull
type <---> trait 🔗
impl Trait_X for Type_Y
Type_Y -> 基本类型,组合类型struct/enum, 闭包,ref,ptr, trait。
Trait_X -> ...
给标准库中的集合直接实现 标准库中的trait是不允许的。
struct MyVec(Vec<i32>);
impl std::fmt::Display for MyVec {
...
}
泛型 🔗
struct BagOfHolding<T> {
item: T,
}
let bag_in_bag = BagOfHolding {
item: BagOfHolding{item: "duang"},
}
println!("{}", bag_in_bag.item.item);
写算法需要熟练掌握的模块 🔗
Option, Result, mem, ptr, vec, String, str, iter。
Rc, Arc, Cell, RefCell, Box
//char 长度都为 4 字节
let chars = "你好 🦀".chars().collect::<Vec<char>>();
let a = 42;
let memory_location = &a as *const i32 as usize;
todo Cell Vs RefCell UnsafeCell(&T --> &mut T)
Cell 不能get到inner ref, 所以修改inner是ok的
impl !sync for Cell
Rc vs Arc
std::ptr::NonNull
Option 会用到的API 🔗
as_ref(): Converts &Option<T> to Option<&T>.<br> as_mut(): from&mut Option->Option<&mut T>`
Option 与 Result 常见方法的对比:
| Option |
函数的功能 | Result<T, E> | 函数的功能 |
|---|---|---|---|
| is_some(&self), is_none() | is_ok(&self), is_err() | ||
| ok_or |
->Result<T, E> | ok(self) -> Option |
Result->Option |
| as_ref(&self) -> Option<&T> | &Option<T -> Option<&T> | as_ref(&self) ->Result<&T, &E> | |
| as_mut(&mut self) -> Option<&mut T> | &mut Option |
as_mut(&mut self) -> Result<&mut T, &mut E> | |
| map<U, F>(self, f: F) -> Option where F: FnOnce(T) -> U | consume the orignal | map(self, op: F) | Result<T, E> -> Result<U, E> |
| as_deref(&self) -> Option<& |
&Option<T -> Option<&T::Target> | as_deref(&self) -> <& |
Result<String, u32> -> Result<&str, &u32> |
| as_deref_mut(&mut self) -> Option<&mut |
&mut Option |
as_deref_mut(&mut self) | |
| expect(self, msg: &str) | may panic | expect(self, msg: &str) -> T | may panic |
| unwrap(self) -> T | my panic | unwrap(self)->T | may panic |
| unwrap_or_default(self)-> T where T: Default, | no panic | ||
| take(&mut self) -> Option |
leave none, return Option | ||
| replace(&mut self, value: T) -> Option |
change and return old value |
enum Option<T> {
None,
Some(T),
}
fn as_ref(&self) -> Option<&T>
let text: Option<String> = Some("Hello, world!".to_string());
// First, cast `Option<String>` to `Option<&String>` with `as_ref`,
// then consume *that* with `map`, leaving `text` on the stack.
let text_length: Option<usize> = text.as_ref().map(|s| s.len());
println!("still can print text: {text:?}");
fn as_mut(&mut self) -> Option<&mut T>
expect() // panics with a provided custom message
unwrap() // panics with a generic message
unwrap_or("abc") // returns the provided default value
ok_or() //Some(v) to Ok(v), and None to Err(err)
filter() //
map() // Option<T> to Option<U>
take() //
replace() //
and_then()
take():
deref_mut()
AsRef 🔗
String -> &str ("abc".to_string().as_ref())
包,模块,可见性 🔗
模块foo可以有2种表示: 一个名为 foo.rs 的文件; 在名为 foo 的目录,里面有一个叫 mod.rs 文件。
std::cmd包 🔗
整个逻辑只在/library/core/src/cmp.rs中定义清楚。
//std::cmp::Eq
pub trait Eq: PartialEq<Self> {}
//std::cmp::PartialEq
pub trait PartialEq<Rhs = Self> where Rhs: ?Sized,
{
//Required Methods
fn eq(&self, other: &Rhs) -> bool;
}
//std::cmp::PartialOrd
pub trait PartialOrd<Rhs: ?Sized = Self>: PartialEq<Rhs> {
//Required Methods
fn partial_cmp(&self, other: &Rhs) -> Option<Ordering>;
}
//std::cmp::Ord
pub trait Ord: Eq + PartialOrd<Self> {
//Required Methods
fn cmp(&self, other: &Self) -> Ordering;
fn max(self, other: Self) -> Self { ... }
fn min(self, other: Self) -> Self { ... }
fn clamp(self, min: Self, max: Self) -> Self where Self: PartialOrd<Self> { ... }
}
pub enum Ordering {
Less = -1,
Equal = 0,
Greater = 1,
}
泛型编程 🔗
是否有必要在此刻就把类型定死?
通过泛型静态绑定推迟到编译期确定类型,trait obj推迟到运行期确定类型。
类型的嵌套 🔗
String, Vec<T>, Box<T>, Unique<T>, *const T
dyn (Fn() + Send + 'static)是什么意思?
Fn() 不带参数和返回值的闭包,可以被Send到其他线程中,具有捕获变量的所有权。 ’static是指没有对上下文中的非静态变量进行引用。
Any trait 🔗
反射。 Deref, Drop, Copy, Any trait
Send & Sync
?Size
Fn FnMut FnOnce 区别 🔗
闭包去实现哪种Fn, FnMut, FnOncetrait与move无关,与如何捕获变量无关。
Fn FnMut FnOnce与闭包如何使用捕获到的值有关。
闭包是trait object, 也是DST类型的。
返回闭包: -> Box<dyn Fn(i32) ->i32>
闭包就是捕获外部变量的结构体,加一段代码
closure 是结构体(捕获的变量)的语法糖,闭包逻辑部分在代码段中。
闭包的调用: Fn::call(第一个参数就是闭包结构体...) Fn:call_
// 移走匿名结构体中变量的closure实现 FnOnce
FnOnce::call_once(self, args: Args) -> Self::Output;
// 不会移走匿名结构体中变量但修改的closure实现 FnMut
FnMut::call_mut(&mut self, args: Args) -> Self::Output;
//不会修改匿名结构体中变量的closure实现 Fn
Fn: call(&self, args: Args -> Self::Output);
在rust中,函数和闭包都是实现了Fn、FnMut或FnOnce特质(trait)的类型。 任何实现了这三种特质其中一种的类型的对象,都是 可调用对象 ,都能像函数和闭包一样通过这样name()的形式调用, ()在rust中是一个操作符,操作符在rust中是可以重载的。 rust的操作符重载是通过实现相应的trait来实现,而()操作符的相应trait就是Fn、FnMut和FnOnce, 所以,任何实现了这三个trait中的一种的类型,其实就是重载了()操作符
rust 生命宏, 过程宏, rust中的插桩 🔗
why 为什么需要宏? 宏是扩展语言的一种方式,元编程。
声明宏: 对代码模版做简单替换。 macro_rules!。 过程宏: 可以深度定制和生产代码。
利用过程宏修改ast来实现fn进入和出来时打印日志。
子类型, 类型产生器, 协变,逆变,不变 🔗
T: U , a: U, b: T
&T vs &mut T 🔗
&T: 共享引用, &mut T 独占引用
rust 内存模型 🔗
compare_exchange , compare_exchange_weak
std::sync::atmoic, memory ordering
- acquire 用于load, 读
- release 用于store,写
- seq 相当于memory barrier
操作符号重载 🔗
rust中的操作符几乎都与一个trait相对应。解引用*x, 重载std::ops::Deref
不可重载的运算符: &, &mut
哪些操作可能会ownership move ? 🔗
所有权,move语义,借用,生命周期的推进关系
为了适应内存的ownership&move语义,引入borrow & reference。
带来新问题是所有权和使用权分隔了,让引用和原始值的内存空间独立了。
但是两者具有逻辑关系上的约束: 引用只能在原始值存在的前提下才有意义。
引入生命周期lifetime到语法中。'a 在函数的入参和返回值之间建立生命周期约束
静态生命周期 ‘static, 全局变量,静态变量,字符串字面量 , 函数指针(进程的虚拟地址空间:BSS, Data, Text, Heap(leaked))
例如: 一个fn返回了引用,如果是引用fn的local变量则❌,如果是引用fn的参数,那么compiler需要知道传入的参数的存活时间是否支撑这个引用的有效性(新引用的生命周期只能是被引用值的生命周期的区间子集)。
Copy:复制语义非move语义。Clone用从&T中创建副本T。
copy是浅拷贝,是编译器自动调用。 clone是深拷贝,程序员手动调用。
- let binding
- 传参数到function
- match 表达式
- methods。 impl block里任何method第一个参数是self
- closure中
特殊例子:
let s = Some(String::from("hallo"));
// _s 会发生绑定, 所有权移动
if let Some(_s) = s {}
// _ 不会绑定 所有权没有移动
if let Some(_) = s {}
rust中的类型转换 🔗
as 转换。
300_i32 as i8 == 44
tryinto转换, use std::convert::TryInto。
as, tryinto 只能运用在数值类型上。
通用转换。unsafe{}, Transmutes。std::mem::transmute<T, U> 将T转为U类型。std::mem::transmute_copy<T, U>
rust源码库中的重导出 🔗
疑问:std::ops::Add or core::ops::Add, std::ops::Fn, core::ops::Fn ? 🤔️ 解答: std reexports everything from core, via pub use
cargo 🔗
cargo install 安装二进制crate。 可执行的二进制文件默认存放在A=$HOME/.cargo/bin下面。 如果A在$PATH下,那么cargo install后的二进制可直接在命令行中运行。
IDE: vscode 插件: rust-analyzer(用), Rust(别用, 与rust-analyzer有冲突)。
rustup 管理rust编程环境。 rustup which rustc 查看rustc的位置 rustup component add rust-src 下载rust源码。
数组array的类型: 元素类型+数组长度(长度也是类型的一部分)[Type; N] , [1, 2, 3], [val; n]。
全局变量:编译期初始化的全局变量。 const , static;运行期初始化的全局变量。lazy_static, Box::leak()
在vscode下配置rust项目的阅读和debug 🔗
rust-analyzer failed to load workspace: Failed to read Cargo metadata from Cargo.toml file
solved:Update the Rust to satisfy the new edition 2021.
rustup default nightly && rustup update