rust相关的环境问题 + 类型系统疑问

· 5744 words · 12 minute read

属于名词解释性的文章,知道就是知道,知道是设计者是这么做得而已。。。balabala。不属于原理性的知识。

阅读rust-lang时遇到的问题 🔗

  原因是git大仓下有些submodule, 这些submodule在clone时并不会随大仓一起下载到本地。
  本地调试 rustc -g main.rslldb mainb mainrunstep

单步调试找不到源代码? 🔗

  遇到调用库函数时进入到汇编, 未跳到源代码的位置。

//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"
    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>> 一个RefCell 可以有多个所有者。 RefCell<Rc<T>>

Cell, RefCell在功能上没有区别。
Cell 使用于 T实现了Copy的情况
RefCell 内部可变, 一个borrow_mut修改后,其他引用可以看到修改。

Rc --downgrade() --> Weak
Weak -- Upgrade() --> Option

Box -> Unqiue -> NonNull --> *const T, *mut T

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 TT,从而让 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 no sync -> RwLock no sync -> Arc is not send and not sync.

Sender is "thread safe" in that it can be sent between threads, but it cannot be shared between threads.

  • 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 , std::ptr::Unique

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(self, err: E) -> Result<T, E>() ->Result<T, E> ok(self) -> Option, err(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 -> Option<&mut T> 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<&::Target> where T: Deref, &Option<T -> Option<&T::Target> as_deref(&self) -> <&::Target, &E> Result<String, u32> -> Result<&str, &u32>
as_deref_mut(&mut self) -> Option<&mut ::Target> where T: DerefMut, &mut Option -> Option<&mut T::Target> 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{}, Transmutesstd::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
solvedUpdate the Rust to satisfy the new edition 2021.
rustup default nightly && rustup update