1. rust: https://doc.rust-lang.org/book/title-page.html 2. cargo: https://doc.rust-lang.org/cargo/index.html ------- - 使用mut修饰变量可变时,表示该变量的内容可变 -- 既包括栈上的值也包括指针指向的堆上对象 - 普通变量的引用就是指向变量的指针,而切片引用是一个胖指针(一个结构体)维护着指向切片起始位置的指针和切片长度 -- 引用是否可变、是否超出生命周期等等检查都是编译期做的,编译产物其实就是一个裸指针或是一个结构体对象 - 可变变量同时只能存在一个可变引用或者多个不可变引用,不可变变量只能有不可变引用 -- 不可变引用表示不能通过该引用修改变量的值,可变引用表示可以通过该变量修改变量的值,但是引用本身是否可以指向别处要看该引用是否被mut修饰 - 引用的作用域结束的位置不是花括号的位置,而是最后一次使用的位置 -- 所以对于一个变量的多个可变引用,只要两个引用的使用位置没有交集,那么编译可以通过 - mut修饰的是它右侧变量的数据(栈和堆上数据)是否可变 -- 如let b = &mut a;表示b是可变引用,可以通过b修改a的数据,但是因为b本身没有被mut修饰,所以b本身是一个不可变变量,也就是b不能指向别处,若要b可以指向别处,则b也需要被mut修饰,写为let mut b = &mut a; - *const T和*mut T:表示指向T类型变量的原始指针:1. *const T为不可变指针,表示不可以通过该指针修改指向的数据,但是该指针是否能指向别处要看该指针变量是否被mut修饰、2. *mut T为可变指针,表示可以通过该指针修改指向的数据,但是该指针是否能指向别处要看该指针变量是否被mut修饰 -- 原始指针在编译期间不会进行任何安全检查,包括所有权、生命周期、内存回收、指针是否有效、并发安全等,它就和C的指针完全一样,所有操作都需要自己维护,原始指针必须在unsafe代码中使用 - &T和&mut T:表示指向T类型变量的安全指针(引用):1. &T为不可变引用,表示不可以通过该引用修改指向的变量,但是该引用是否能指向别处要看该指针变量是否被mut修饰、2. &mut T为可变引用,表示可以通过该引用修改指向的数据,但是该引用是否能指向别处要看该指针变量是否被mut修饰 -- 安全指针在编译期间会被进行安全检查 - 常说的切片实际是指切片的引用(如&str、&[i32]等) -- 实际上切片是str([u8]的别名)、[i32]这样的写法,切片只是一个概念,表示的是在堆上由任意数量的T类型元素组成的连续序列,并不能直接使用,切片都是动态大小类型,在编译期无法确定大小,所以不能在栈上进行分配(只能在堆上分配),因此无法直接使用,而是应该通过引用来使用它们 - 数组其实就是确定大小的切片,如[i32; 5]表示包含5个i32类型数据的切片,数组在编译期可以确定大小,默认在栈上分配 -- 若要在堆上创建数组,则需要使用Box<[i32]>来进行创建 - 字符串底层使用u8来存储数据 -- 对于"hello"这种字面量底层是[u8]切片,对于String这种可变字符串内部维护的是Vec - char是u32类型的别名,用来存储一个字符,使用Unicode编码,足以表达所有的字符 -- 对于字符串s,应该使用s.chars()函数来获取其中的每一个字符(u32),而s.bytes()获取的是其中的每一个字节(u8) - Box、Rc、Arc是一个结构体,内部通过指针成员维护指向对象的指针,所指向的对象一定是在堆上分配的,持有指向对象的所有权;Rc、Arc的实现原理就是在clone函数执行时不是真正去clone对象对象,而是仅递增Rc内部的引用计数,在一个Rc变量超出作用域后被销毁,自动递减其内部的引用计数,计数减到0时销毁内部持有的对象 - Rc、Arc只实现了Deref特征,所以对它们进行解引用时调用的是deref函数返回持有对象的不可变引用,所以不能使用Rc和Arc直接修改其指向的对象 - Box实现了Deref和DerefMut两个特征,所以对于非mut修饰的box变量,调用的是deref函数返回的是指向对象的不可变引用,不能修改内部对象;而对与mut修饰的box变量进行解引用所调用的是deref_mut函数,返回指向对象的可变引用,可以对内部对象进行修改 - Rc、Arc若要能够修改堆上对象,其堆上所存对象必须是使用Cell、RefCell以及Mutex进行包装的对象 -- 单线程:Rc + RefCell、多线程:Arc + Mutex - Rc、Arc、Cell、RefCell、Mutex、RwLock等结构具有运行时开销,因为它们的引用计数管理(Rc、Arc)、数据拷贝(Cell)、可变借用检查(RefCell)、加锁(Mutex、RwLock)等都是在运行时而不是在编译期 - 使用Rc创建多个对同一对象的所有权变量,和直接对某一个变量创建多个只读引用有什么区别呢?-- 引用需要依赖原始变量的生命周期,引用不能在原始变量已经超出生命周期后独立存在,而Rc的变量是对变量单独持有所有权的,所以可以单独存在,在最后一个对变量的所有权Rc变量生命周期结束后变量才被自动销毁 - 闭包默认捕获外部变量的不可变引用,若在闭包内对某个变量进行修改则捕获的是其可变引用,但要保证闭包中的引用生命周期比变量的生命周期短,所以多线程传入闭包捕获引用的话会编译报错,因为变量的生命周期不一定比引用的生命周期长。若要在闭包中直接捕获外部变量(获得其所有权),则需要在闭包前添加move关键字,这时外部变量的所有权会被转移到闭包内部,闭包外将不能再使用被捕获的变量 -- 无论是捕获引用还是所有权,闭包函数只捕获在闭包中被使用到的变量,闭包中未使用的变量则不会被捕获 - 闭包使用move捕获外部结构体变量时,会有disjoint-capture优化,即按字段使用结构体数据而不是默认直接捕获整个结构体的所有权(可以减少栈数据的拷贝大小,由原来要拷贝整个结构体对象优化为只拷贝被使用的字段),但这个优化只适用于实现了Copy特征的字段,在闭包内使用了某个字段,就将这个字段拷贝一份给闭包使用,结构体变量的所有权还是在外部没有被移动。但一旦在闭包内使用了未实现Copy特征的字段,那么整个结构体变量的所有权将被闭包捕获 - Rust中所有权的移动其实就是栈上数据的拷贝和对栈上原数据的无效化处理(若是堆上数据其实就是栈上指针的拷贝)(原数据只是编译层面进行了无效化标记,禁止继续访问,实际运行时它在栈上的内存没有任何变化) -- 比如将a的所有权移动给b,其实就是将a所对应的栈上数据拷贝给b,然后将a的栈上数据做无效化处理 - Deref、DerefMut特征:用于在对对象进行解引用时自动调用实现的deref和deref_mut函数,返回某个对象的引用,deref和deref_mut函数会在【使用.调用函数、使用.访问成员属性、使用*进行解引用】等时自动调用,若只实现Deref特征,则无论变量是否为mut的都是调用deref返回对象的不可变引用(如Rc、Arc),若两个特征都实现了,则mut变量调用deref_mut,非mut变量调用deref(如Box) -- 如 *rc_param -> *(rc_param.deref()),rc会在deref函数中返回内部变量的不可变引用 - Send特征:表示实现该特征的类型的变量可以在多线程间转移所有权,比如使用move将外部变量所有权移动到闭包内部 -- 基本所有类型都实现了Send特征,但是Rc就没有实现,因为Rc的引用计数是非线程安全的,如果clone多个Rc并将它们的所有权移动到多个线程,那么Rc的引用计数的操作会在多线程间出现并发问题,如Rc超出生命周期后会对计数进行减一,这可能出现并发问题 - Sync特征:表示实现该特征的类型的变量可以在多线程间共享引用(即其不可变引用&T是线程安全的,可以被多线程并发访问)-- 如创建一个子线程传入闭包函数,默认闭包函数捕获外部函数的引用,若一个类型没有实现Sync特征且其引用在子线程中被使用了,那么会编译失败,因为它的引用不能在多线程中并发访问 - Copy特征:表示该变量在会发生所有权转移的时候不是进行所有权的转移(所有权转移也是栈拷贝,只是实现了Copy特征的变量,对原数据不进行无效化标记,可以继续使用),而是直接进行栈拷贝复制一份数据使用,原变量的所有权还是归原变量所有 -- 如使用let创建一个新的变量将原变量赋值给它、使用move将变量移动到闭包内等 - Clone特征:表示可以对变量进行复制,不过需要显式地调用clone函数,且可以自定义clone函数的实现,若使用编译器自动生成的clone函数,则所有变量都需要实现了Clone特征,否则需要手动clone实现函数 - Drop特征:表示对象会在离开作用域时被调用drop方法进行析构(也就是析构函数),Rust自动为几乎所有类型都实现了Drop特征,因此自定义的结构体没必要专门实现Drop,除非在析构时有特殊操作(如Rc在clone时增加引用计数,在drop时减小引用计数,只有当引用计数减为0时才释放内存堆上对象的内存) -- Drop特征和Copy特征互斥,一个类型不能同时实现两个特征 - const用来定义常量值,在会编译时直接内联,在每次使用的地方直接对变量进行栈拷贝,相当于每次使用都是一个新的变量 -- 只有实现了Copy特征的类型才能被定义const常量,因为需要在编译时确定和直接分配栈空间 - static用来定义静态全局变量,全局分配内存初始化一次,每个值只有一个实例,内存地址固定,存放在.data段 - 生命周期注解用于标识函数的引用类型出入参和结构体引用类型成员的生命周期,防止指针指向已经释放的数据 -- 函数的生命周期注解有些情况可通过编译器优化自动标注,结构体生命周期注解在结构体有引用类型成员时必须显式标注 - 函数有引用类型出入参时有些情况编译器会优化,所以可以省略生命周期注解,具体有以下三种:-- 除以下三种情况外,其它情况都需要手动标注生命周期注解,包括:1. 需要使用多个函数入参生命周期对函数返回值的生命周期进行约束、2. 函数的多个引用类型入参之间进行生命周期约束、3. 需要对函数范形内的引用成员进行约束 - 函数有一个或多个引用类型入参,没有引用类型出参,且目标上各个入参的生命周期不需要进行相关约束 -- 编译器会为每个入参补充单独的生命周期注解 -- 如:fn foo(x: &i32, y: &i32) -> fn foo<'a, 'b>(x: &'a i32, y: &'b i32) - 函数只有一个引用类型入参时 -- 编译器会将这个入参的生命周期应用到函数的所有引用类型出参 -- 如:fn foo(x: &i32) -> &i32 -> fn foo<'a>(x: &'a i32) -> &'a i32 - 函数有多个引用类型入参,但其中一个是&self(即该函数是一个结构体的方法)-- 编译器会将self的生命周期应用到方法的所有引用类型出参 -- 如:fn foo(&self, x: &i32) -> &i32 -> fn foo<'a, 'b>(&'a self, x: &'b i32) -> &'a i32 - 函数生命周期注解:-- 用于对函数多个引用类型入参之间的生命周期进行约束,或当函数返回引用类型时使用入参的生命周期对返回值的生命周期进行约束,或对函数范形类型内的引用成员进行生命周期约束 - &'a T和&‘a mut T:表示指向具有'a生命周期的T类型对象的安全指针(引用),即指针指向的数据具有'a生命周期,也就代表了这个指针在'a生命周期内一定是有效的 -- 用于对引用入参之间的生命周期关系进行约束,或对引用入参和函数返回值直接的生命周期进行约束 - T: 'a:表示T类型内的所有引用成员指向的数据的生命周期必须大于等于生命周期'a -- 用于对函数范形类型内的引用成员进行生命周期约束 - 结构体生命周期注解:-- 用于使用结构体成员中的引用类型成员的生命周期对结构体对象本身进行生命周期的约束,只有结构体中有引用类型成员时才需要用,因为要确保结构体内的指针成员要在结构体对象存活期间始终是有效的 -- 所以要要保证指针成员的生命周期比结构体对象本身的生命周期长,不然会出现悬垂引用 - 成员生命周期注解:用来使用结构体引用成员的生命周期约束结构体对象本身的生命周期 -- struct Foo<'a, 'b> {id: &'a i32, name: &'b String} 表示每一个Foo对象的生命周期都必须大于等于'a和'b生命周期 - 方法定义中的生命周期注解:用来使用结构体引用成员的生命周期对结构体方法的引用类型出入参或方法范形类型内的引用成员进行生命周期约束 -- impl<'a, 'b> Foo<'a, 'b> {} 在结构体的方法中使用'a和'b对各个方法的引用类型出入参和方法范形内的引用成员进行约束 - Rust不支持继承,使用继承有两种原因,rust都有对应的实现方案 -- 1. 代码重用:可以将公共函数抽象到trait中默认实现,然后将要实现该函数的结构体实现这个特征,可以为结构体实现同名函数来重写特征的默认实现。2. 多态:使用dyn trait来表示实现指定某个特征的结构体