zoukankan      html  css  js  c++  java
  • Rust零碎总结

    1.Rust里没有null的概念,但是实际上有很多地方是需要null的概念的,这个时候就可以用Option来代替,它是泛型T的一个包装类,就是C#里的int?或Java里的Optional;

    【但反序列化貌似是可以没有null概念,没有这个属性用默认值就好了,Java的Json反序列化貌似本身就是这样做的】

    2.rust里没有分号结尾的代码叫表达式expression,如a+b,它能够被自动"return",有分号的叫语句statement;

    3.Rust里void类型是叫单元类型,一般用()表示,也可以忽略;

    4.Rust里有引用和解引用的概念,引用变量销毁时由于它没有指向对象的所有权,所以不会销毁对象(即不会delete),而解引用是获得了所有权所以在其声明周期结束后会主动销毁指向的对象;引用用&,解引用用*;

    5.Rust对于基础类型是复制语义,而对于复合类型是移动语义;

    6.移动后的变量它的所有权将发生转移,如let a = b,此时之后b销毁了不会释放其之前指向的地址空间;

    7.所有的变量都有隐式的生命周期,生命周期一过,那些拥有所有权的变量将会释放它们指向的地址(编译器帮我们做,不需要像C++一样手动delete)

    8.rust里默认的变量(绑定一个值)是不可变的(所以它的借用或叫引用也必须是不可变的,当变量是不可变变量时,它可以在一个作用域里拥有多个不可变借用;

    而如果变量是可变的(加了mut),那么在一个作用域里只能有一个可变借用;(有点像读写锁)

    9.变量默认不可变,而不是不能移动所有权;

    10.虽然非mut的变量,不同获取其mut的引用,但是可以将非mut变量移动所有权给mut变量,比如let a = S{};let mut b = a;

    11.Rust里mod.rs和lib.rs都是很特殊的文件,最好就不要定义某个mod文件是mod.rs【对比student.rs】,mod.rs是用来描述一个模块的,比如model模块,可以创建一个model目录,然后里面添加一个mod.rs来描述model目录以及声明model里哪些子模块或者model模块里的哪些方法或struct等可以导出到外部使用;

    而lib.rs则是创建lib项目时会在src根目录下,它用来描述src根目录下哪些rs文件可以被导出【其实可以理解为src是一个mod目录,这个时候lib.rs就类似mod.rs了,不过一般不在lib.rs里定义类型或函数】

    12.Rust用cargo build来生成库文件或可执行文件,默认是debug的,可以通过cargo build --release来生成release文件;【cargo build会自动去下载依赖包及更新Cargo.lock文件】

    13.Rust可以通过include_str!(...)宏在编译期间从配置文件里获取数据赋值给常量;

    14.Rust的Mutex如果要在多线程里实现同步区域的锁(本质上是锁对象/变量),必须是let mutex_guard = mutex.lock().unwrap();,注意这个mutex_guard必须留着哪怕不用,如果用了let _ = mutex.lock().unwrap();那么将会导致mutex_guard立刻释放从而获得锁后又立刻释放锁,因而加锁失败;锁的释放就是通过每次mutex_guard的销毁实现的;

    15.Rust里类型是包含三部分的,第一部分是类型基础名(想不到更好的描述词。。后面解释是啥意思),第二部分是泛型名(或叫泛型类型),第三部分是生命周期标志;

    比如struct Kk<'a T> {..},这里基础类型名是Kk,泛型是T,声明周期标志是'a;这里需要注意的是泛型类型名不是必须的,只有泛型类型才会有;而基础类型名和声明周期标志是一定必须的,但是声明周期标志大多数情况可以省略系统自动帮我们搞定

    对于HashMap或者LinkedList之类的key或value的类型它们必须是确定的唯一类型,std::any::Any不能作为key或value类型(Any其实和泛型差不多,但是又有很大的区别,Any确实作为函数参数的时候可以动态确定类型,但是它不具备反射功能,因此

    要动态确定的类型必须先在代码里写明可能是哪些类型然后来转换为这些类型,这些类型可以没有任何相关性【比如两种entity,any是A则返回A的a属性,any是B则返回B的b属性,这点是泛型做不到的;但是any是需要在代码里写好是可能为A或B类型】)

    因此LinkedList之类的元素的类型是唯一确定的,自然就包括了类型的生命周期标志(不写也是存在被编译器推断的标志),所以不能往List或Map等添加两种不同声明周期标志的类型元素,哪怕元素的基础类型名和泛型都一样但是声明周期标志不一样也不行;

    举个栗子:

    let mut hash_vec: HashMap<u32, &str> = HashMap::new(); 
        let str1 = "ssf".to_string();
        hash_vec.insert(3, str1.as_str());
        let str2: String = "ccc".to_string();
        hash_vec.insert(4, str2.as_str());
        println!("{:?}", hash_vec);

    let mut list: Vec<&str> = Vec::new(); 
        let str1 = "ssf".to_string();
        list.push(str1.as_str());
        let str2: String = "ccc".to_string();
        list.push(str2.as_str());
        println!("{:?}", list);

    上面的代码不会报错,因为我们添加的两个pair的key的生命周期都是推断为'static而value推断为假设叫'a(名字不重要重要的是两个元素的value生命周期一致)所以编译通过;

    而如果是这样的代码:

    let mut hash_vec: HashMap<u32, &str> = HashMap::new(); 
        hash_vec.insert(3, "aaa");
        {
            let str2: String = "ccc".to_string();
            hash_vec.insert(4, str2.as_str());
        }
        println!("{:?}", hash_vec);

    let mut list: Vec<&str> = Vec::new(); 
        list.push("aaa");
        {
            let str2: String = "ccc".to_string();
            list.push(str2.as_str());
        }
        println!("{:?}", list);

    则会报错,因为第二个元素的value生命周期和第一个元素的不一致,也就是说尽管第二个元素类型也是&str,但是其实不是一个类型(当然报错提示是第二个元素value活的不够长)【看后面的解释,这个结论其实有问题。。】

    【好吧,上面的生命周期标志的理解可能不太符合实际测试情况(留着作参考价值)。。。,继续测试如下】

    let mut hash_vec: HashMap<u32, &str> = HashMap::new(); 
        hash_vec.insert(3, "aaa");
        let str2: String = "ccc".to_string();
        hash_vec.insert(4, str2.as_str());
        println!("{:?}", hash_vec);
    let mut list: Vec<&str> = Vec::new(); 
        list.push("aaa");
        let str2: String = "ccc".to_string();
        list.push(str2.as_str());
        println!("{:?}", list);

    这段代码不会报错,但是显然是不符合我之前的结论的,第一个元素的value生命周期明显是static,第二个则不是,但是也没有报错,所以从这里来看对于map或list并不是说元素类型必须完全一致,而是说每个元素的生命周期大于等于map或list,否则我们在后续调用map.get(1)如果正好就是生命周期不够的那个元素,比如上上个示例的4, str2.as_str(),那么就会出现内存安全问题(引用了销毁的元素)

    继续看代码:

    
    
    #[derive(Debug)]
    struct Kkk<'a, 'b> {
    pro1: &'a String,
    pro2: &'b i32,
    }
    ---------------------------------
    { let sse
    = 3; let mut k = Kkk {pro1: &"sss".to_string(), pro2: &sse}; println!("{:?}", k); { let ssk = 4; k.pro2 = &ssk; println!("{:?}", k); } //println!("{:?}", k); }

    这段代码不会报错,因为后续没有再用到k了,所以尽管ssk的生命周期比k要小,但是也不会报错(这个是rust编译器优化的地方,估计以前也是报错的),而把注释的代码反注释就会报错,提示ssk存活的不够长;注意这里哪怕是输出k.pro1没有用到pro2也报错,这个是rust编译器还没那么智能(以后检测粒度更小后可能不会报错)

    16.对于这样的代码:

    let kkk = "sss";

    这里"sss"返回的是一个胖指针,即地址和长度,地址就是指向"sss"这个&str的str对象的起始地址和字节长度(所以str对象是一块连续的内存);但是这个str对象是只存了sss三个字符的;

    17.rust里不允许直接使用不确定类型,比如str,因此*"sss"会报错是因为它产生了一个str类型而不是因为不能对指向static对象的借用进行解引用;

    18.自动解引用可以通过实现Deref来做到,比如我们一个指针它指向Foo类型对象(包括智能指针和裸指针)有实现test方法,如果我们为Foo实现了Deref,那么我们调用Foo指针的test方法时,由于编译器判断这个指针没有test方法,而它指向的Foo对象实现了test方法,就会自动帮我们隐式的加上解引用操作符;编译器的逻辑是:发现我们代码里的表达式里指针使用不正确,比如类型不一致,没有想关的属性,没有相关的方法,就会尝试为这些不正确的指针进行自动解引用(假设实现了,还有个DerefMut);但是有一些情况编译器不会帮我们自动解引用,比如指针(智能指针)和其指向的类型都有某个方法时,然后这里调用的这个方法我们需要的是指向对象的,但是编译器不知道因此不会自动解引用。

    19.解引用后不是说原来的对象的所有权就被转移了(如果是实现了Copy则是复制),比如我只是解引用后调用对象的方法(且该方法里是&self而非self),或者是println!("{}", *foo)这样的宏调用解引用是不会发生所有权转移的【因为这个宏其实也是调用foo的Display trait的实现方法,是&self】,但是假设Foo没有实现Copy trait,然后调用了一个参数类型是Foo的函数就会发生所有权转移,如果是直接调用是可以的,比如test(foo);【前提是上面没有其他地方有获取foo的借用】,后面的代码里就不允许用foo了,但是如果是let k = &foo;test(*k);则不允许move,因为借用没有权利移交所有权,这样调用则可以test(foo),只要后面不再用foo和k即可;

    【经过测试,函数里无法对借用参数解引用后然后移交所有权,因为借用没有权利移交所有权【但是如果参数是Box智能指针则可以在函数内let k: Foo = *box来移交所有权,因为box拥有对Foo对象的所有权】(会移交所有权给另一个函数的对象最好创建在堆里能减少内存占用,除非编译器能进行很细节的智能优化【因为移交所有权给参数没有优化的化是发生了数据拷贝的(不过通过Copy trait)】)】

    【经过测试,无法对static对象(没有实现Copy)移交所有权,因为static对象是不能被RAII的,而能移交所有权的对象本身是可以RAII的,所以let k = *"ss"错误其实有两方面,一个是产生了str(当前版本不允许这种unsized类型),一方面就是试图移交static对象所有权,注意这个unsized不是说str没有具体大小,而是指在编译期间编译器无法确定str类型的大小(&str不是只能字面量得到),它不像i32就占4个字节,因此称之为unsized的,一个trait参数也是,由于很多类型都是可以实现该参数trait,因此该参数也是unsized】

    20.基础类型是实现了Copy trait的,所以不能用来做一些解引用测试;

    21.对借用的解引用是不能可能发生所有权转移的,因为借用没有这个权利,但是如果这个指针是Box(借用也是一种指针)则可以通过*box来移交所有权,box通过let k: Foo = *box;移交所有权后不能再使用box(Box的移交所有权和普通变量不一样,普通变量是let m = a,那么a就不能再使用了);

    22.借用可以这么理解,借用每次用的时候(每个用到这个借用变量的地方)都类似我们现实中的别人跑过来看一下源数据是什么样然后用这个数据,(比如数据是一块黑板的内容我是owner可以随时修改,借用[别人]每次用的时候[即调用借用的地方,如println!("{}", borRef)到了这个点借用方就跑我这来看下黑板上的最新数据然后记忆后用它而不是把黑板拿走),因此我是黑板的主人,所以可以随时把这个黑板转给别人让别人成为新主人,如果我转让后,那么借用方就不能再跑我这来记忆黑板数据了(即再调用借用变量);

    我就是那个引用变量名,黑板就是这个变量指向的栈空间,而别人则是借用变量名,对借用变量名的调用则是别人跑我这来看黑板上的数据并记忆到它要用的地方的过程(调用方法则可以理解为在我这申请调用方法后记忆相关数据到要用的地方)

    至于智能指针则多了一个藏黑板的地图;移交黑板则是通过藏黑板地图来实现移交,即let kk = *box;移交黑板后藏黑板地图也就没用了,因此无法继续使用藏黑板地图,即box不能再用(不要杠);

    而别人借用的过程对于Box有两种方式,第一种是let bor = &*box;即我直接告诉它黑板在哪,bor每个用到的地方都会跑到藏黑板的地方临时看一遍黑板上的数据或临时使用一下黑板得到他要的数据记忆后用在它被调用的地方;

    第二种是let bor = &box;即它每次用(每个被调用bor的地方)都跑我这来看一下藏黑板地图然后由地图找到黑板再临时使用下黑板得到他需要的数据他记忆后用在被调用处(这里不要用人的思维说为什么每次都要看地图,第一次看完没记住吗?你就理解为这种bor记忆力很不好必须每次看一遍地图。。);同样的,我也是可以随时转出黑板的(此时地图也将没用了因此别也不能来看地图了,别杠),比如println!("{:?}", bor);,这个代码就相当于别人还要跑我这来看地图或者看我的黑板,在我转出黑板后是不允许的。

    23.解引用不是只能解&这种普通的借用(所以还是就只叫借用比较好,否则人家一听解引用好像是专门解&一样,但是实际上还能解Box这种智能指针),借用和智能指针都是引用的一种

    24.如果要以“字面量”方式生产HashMap可以这么写:

      let x: HashMap<_, _> = vec![("aa", 11), ("bb", 22)].into_iter().collect();
        let x1: HashMap<_, _> = [("a", 1), ("b", 2)].to_vec().into_iter().collect();
        let x2 = vec![("aa", 11), ("bb", 22)].into_iter().collect::<HashMap<_, _>>();
        let x3 = [("a", 1), ("b", 2)].to_vec().into_iter().collect::<HashMap<_, _>>();

     25.Cargo.toml里的version如果不加符号,比如version="0.1.0"那么它等价于version="^0.1.0",即大于等于0.1.0版本小于0.2.0版本,如果非要完全等于0.1.0版本可以version="=0.1.0"

    ^1.2.3 := >=1.2.3 <2.0.0
    ^0.2.3 := >=0.2.3 <0.3.0
    ^0.0.3 := >=0.0.3 <0.0.4
    ^0.0   := >=0.0.0 <0.1.0
    ^0     := >=0.0.0 <1.0.0

     26.实现了Deref和Drop(DerefMut)的就算智能指针,可以用*解引用(书上是这么说的,暂且这么记)【经过测试好像Drop都未必需要手动实现,能Deref就能用*了】

    27.let mut k = 8;let m = &mut k;然后允许let u = *m,(正常情况是不允许的)因为k实现了Copy,所以这里没有移交所有权而是Copy了数据返回;而*m += 1;则是对k的值进行了修改,这个语句没有发生所有权转移,因为没有声明一个变量来将*m移动给它(假设m指向的对象没有实现Copy)

    28.

    let mutex = Mutex::new(0i32);
        let mutex1 = Mutex::new(0i64);
        let mutex2 = Mutex::new(0i64);
        println!("{:?}, {:?}, {:?}", mutex.type_id(), mutex1.type_id(), mutex2.type_id());

    由上面得出mutex和mutex1不是同一个类型,而mutex1和mutex2是同一个类型,尽管它们都是由Mutex包裹起来的(不过看Mutex的类型声明就很明了,Mutex是一个泛型类型)。

    29.Fn(), FnOnce有个很大的区别是FnOnce是只能执行一次,因此对于如下代码是正确的:

    #[derive(Debug, Clone)]
    struct Foo {
        pro1: i32
    }
    
    fn main() {
        let mm = Foo{pro1: 9};
        test1(move || {  // move是因为test1里F是static的缘故
            println!("{},$$$", mm.pro1);
            test2(mm);
        });
    }
    
    fn test1<F>(f: F) where F: FnOnce() + 'static {
        f();
    }
    
    fn test2(fo: Foo) {
        println!("$$%%%{}", fo.pro1);
    }

    但是将test1的FnOnce()换成Fn()则报错,因为f这个closure是可能执行多次的(Fn()),因此在调用test2时会将所有权move给test2的fo,如果执行多次则会多次move显然是不正确的;这里可以在调用test2处改成mm.clone()或者实现Foo的Copy;

    30.static的closure也不是必须显示声明move,比如这种写法是常用的(而且是推荐的)

    let mms = Foo{pro1: 10};
        std::thread::spawn(move || {
            println!("{:?}", mms);
            //let su = mms;  // flag1
        });

    但是如果我们去掉move,则mms其实是外部mms的借用,因此不符合spawn里f是static的约束;

    但是如果我们去掉move,然后取消flag1的注释则代码又运行成功,因为这里我们隐式的告诉了编译器这里需要move 外部mms变量,su的类型就是Foo而非&Foo;不过虽然有这个功能,最好还是显示的写出move(即上面的代码不去掉move且取消flag1不会报错或warning)

    31.Rust的访问权限和Java等不一样,它的struct字段没有所谓私有字段的说法,如果不加pub则字段是mod内可访问(而Java里存在私有字段和default访问权限字段【即包】),而如果用了serde_derive的话不加pub也可以被serde访问的原因是这个宏生成了类似Java getter setter的方法;rust里pub对于struct或方法或字段是一样的,都是针对mod,super,crate,other crate这几种访问权限。

    32.如果是Option可以用if let Some(ss) = option,如果是Result可以用match来分别针对Some和Err来拆箱。

    33.&user.name注意这里不存在移动所有权的说法,user.name确实是获取的name的字段,但是它还没有复制给一个变量,因此没有移动所有权;而let kk = &user.name;在赋值之前是先&获取的是user.name的借用,因此这里没有发生所有权转移。

  • 相关阅读:
    centos 编码问题 编码转换 cd到对应目录 执行 中文解压
    centos 编码问题 编码转换 cd到对应目录 执行 中文解压
    centos 编码问题 编码转换 cd到对应目录 执行 中文解压
    Android MVP 十分钟入门!
    Android MVP 十分钟入门!
    Android MVP 十分钟入门!
    Android MVP 十分钟入门!
    mysql备份及恢复
    mysql备份及恢复
    mysql备份及恢复
  • 原文地址:https://www.cnblogs.com/silentdoer/p/11572757.html
Copyright © 2011-2022 走看看