zoukankan      html  css  js  c++  java
  • rust 高级话题

    rust高级话题

    前言

    每一种语言都有它比较隐秘的点。rust也不例外。

    零大小类型ZST

    struct Foo; //类单元结构 
    struct Zero(
        (), //单元类型
        [u8;0], //0大小的数组
        Foo,
    );//零大小类型组成的类型
    

    动态大小类型DST

    无法静态确定大小或对齐的类型。

    1. 特征对象trait objects:dyn Mytrait
    2. 切片slices:[T]、str

    特征包含vtable,通过vtable访问成员。切片是数组或Vec的一个视图。

    也许你会产生好奇,为什么字符串和特征不能像一般语言设计的那样,设计成一个指针就好,而弄成一个动态大小的类型。这和普通指针有什么不同?

    从程序员的角度出发,所谓动态大小类型是不存在的,因为你不能构造一个动态大小类型的对象出来,不管如何你只能构造"动态大小类型的指针"。动态大小类型更像是一个思维过程的中间产物。

    注意,动态大小类型的指针和普通指针是不同的:

    1. 动态大小类型指针是胖指针,有地址,还有大小,也就是多维的。

    如&str 可以想象成:

    &str{
        ptr:*u8,
        size:usize,
    }
    

    既然如此,那么解引用*&str就是无意义的,因为它丢失了对象的大小。这个角度去理解动态大小类型,或者比较具体。

    rust中的动态大小类型,其本质是将原本对象中的大小信息,直接放到指针里面,形成一个胖指针,而对象自身是不包含大小的。这是合理的,比如c语言中的字符串,本质就是一个结束的字符串序列而已,并不包含什么大小字段,也不可能要求所有字符串都要带一个大小的字段。

    为了类型安全,大小信息又是必要的,因而对这类基础类型做一个抽象,然后用胖指针来指向,不失为一个合理方案。

    特征对象的指针也是如此,rust中作为一个胖指针来实现,因此特征对象本身就成了无法构造的动态大小类型了。

    对于特征对象,rust有两个特殊关键字支撑其行为(impl 和 dyn):

    1. impl 静态分发,泛型技术。(不要和impl xxx for y搞混)
    2. dyn 动态分发,即指针。(用dyn能更好的对应静态分发的写法)
    trait S{fn so(&self);};
    impl S for i32{fn so(&self){println!("i32");}}
    impl S for &str{fn so(&self){println!("&str");}}
    
    //静态分发,0成本
    fn f(a:impl S)->impl S{
        a.so();
        1
    }
    f("hi").so();
    
    //动态分发,少量代价
    fn f2(a:&dyn S)->&dyn S{
        a.so();
        &"hi"
    }
    f2(&1).so();
    

    正确的安装方法

    rust是一个快速变化的语言和编译系统,建议用最新版,否则可能出现兼容性问题而无法通过编译。

    rustup 管理工具链的版本,分别有三个通道:nightly、beta、stable。如上所述,建议同时安装nightly版本,保持最新状态。

    wget -O- https://sh.rustup.rs |sh  #下载安装脚本并执行
    

    安装之后,就可以用rustup命令来控制整个工具链的更新了。

    rustup toolchain add nightly #安装每夜版本的工具链
    rustup component add rust-src #安装源代码
    cargo +nightly install racer #用每夜版本的工具链安装racer,一个代码补全的工具。因为当前只支持每夜版本
    rustup component add rls # 这是面向编辑器的一个辅助服务
    rustup component add rust-analysis #分析工具
    
    #vscode : 搜索插件rls安装即可
    

    结构体

    struct 结构体是一种记录类型。成员称为域field,有类型和名称。也可以没有名称,称为元组结构tuple strcut。 只有一个域的特殊情况称为新类型newtype。一个域也没有称为类单元结构unit-like struct。

    类别 域名称 域个数 写法举例
    一般结构 >1 strcut S{x:i32,y:&str}
    元组结构 >1 strcut S(i32,&str)
    新类型 1 struct S(i32)
    类单元结构 0 struct S

    枚举和结构是不同的,枚举是一个集合(类似c语言种的联合union,变量的枚举成员可选,而不是全体成员),而结构是一个记录(成员必定存在)。

    作为一个描述力比较强的语法对象,结构很多时候都在模拟成基础类型,但是更多时候是具备和基础类型不同的特征,而需要由用户来定制它的行为。

    #[derive(Copy,Clone,Debug)] //模拟基础数据类型的自动复制行为,Debug 特征是为了打印输出
    struct A;
    
    let a = A;
    let b = a;//自动copy 而不是move
    println!("{:?}", a);//ok
    
    

    复制和移动

    rust的基本类型分类可以如此安排:

    1. 有所有权
      1. 默认实现copy
        1. 基本数据类型
        2. 元素实现copy的复合类型
          1. 数组
          2. 元组
      2. 没有默认实现copy,都是move
        1. 结构
          1. 标准库中的大部分智能指针(基于结构来实现)
          2. 标准库中的数据结构
          3. String
        2. 枚举
    2. 无所有权
      1. 引用
        1. &str
        2. &[T]
      2. 指针

    基础数据类型,引用类1(引用&T,字符串引用&str,切片引用&[T],特征对象&T:strait,原生指针*T),元组(T2),数组[T 2;size],函数指针fn()默认都是copy;而结构、枚举和大部分标准库定义的类型(智能指针,String等)默认是move。

    注意:

    1. 引用只是复制指针本身,且没有数据的所有权;因为可变引用唯一,在同一作用域中无法复制,此时是移动语义的,但可以通过函数参数复制传递,此时等于在此位置重新创建该可变引用。
    2. 元素必须为可复制的

    特征对象

    特征是一个动态大小类型,它的对象大小根据实现它的实体类型而定。这一点和别的语言中的接口有着本质的不同。rust定义的对象,必须在定义的时候即获知大小,否则编译错误。

    但是,泛型定义除外,因为泛型的实际定义是延迟到使用该泛型时,即给出具体类型的时候。

    或者,可以用间接的方式,如引用特征对象。

    trait s{}
    fn foo(a:impl s)->impl s{a} //这里impl s相等于泛型T:s,属于泛型技术,根据具体类型单态化
    

    引用、生命周期、所有权

    rust的核心创新:

    • 引用(借用):borrowing
    • 所有权:ownership
    • 生命周期:lifetimes

    必须整体的理解他们之间的关系。

    借用(引用)是一种怎样的状态?

    例子:

    • let mut 旧 = 对象;
    • let 新 = &旧 或 &mut 旧;
    操作 旧读 旧写 新读 新写 新副本
    copy
    move
    & ✔1
    &mut ✔1

    注1:旧写后引用立即无效。

    从上表格可以看出,引用(借用)并不只是产生一个读写指针,它同时跟踪了引用的原始变从量是否有修改,如果修改,引用就无效。这有点类似迭代器,引用是对象的一个视图。

    一般性原则:可以存在多个只读引用,或者存在唯一可写引用,且零个只读引用。(共享不可变,可变不共享)

    特殊补充(便利性规则):
    可以将可写引用转换为只读引用。该可写引用只要不写入或传递,rust会认为只是只读引用间的共享,否则,其他引用自动失效(rust编译器在不停的进化,其主要方向是注重实质多于形式上,提供更大的便利性)。

    let mut a = 1;
    let mut rma = &mut a;
    let mut ra = &a;//error
    let ra = rma as &i32; //ok
    println!("*ra={}", ra);//ok
    println!("*rma={}", rma);//ok
    
    *rma = 2; //ra 失效
    println!("*ra={}", ra);//error
    println!("*rma={}", rma);//ok
    a = 3; //ra、rma 都失效
    println!("*ra={}", ra);//error
    println!("*rma={}", rma);//error
    

    用途在哪里?

    let p1 = rma; //error
    let p2 = ra; //ok
    

    即:需要产生多个引用,但不是传递函数参数的场合。如转换到基类或特征接口(如for循环要转换到迭代器)。

    let v=[1,2,3];
    let p1=&mut v;
    let p2=p1 as &[i32];
    
    // 注释掉其中一组代码
    // 1
    for item in p1{}
    println!("{}",p1);//error
    
    // 2
    for item in p2{}
    println!("{}",p2);//ok
    

    引用无法移动指向的数据:

    let  a = String::from("hi");
    let pa = &a;
    let b = *pa; //error
    

    生命周期

    1. 所有权生命周期 > 引用

    引用并不管理对象生命周期,因为它没有对象的所有权。引用需要判定的是引用的有效性,即对象生命周期长于引用本身即可。

    当需要管理生命周期时,不应该使用引用,而应该用智能指针。

    一般而言,我们不喜欢引用,因为引用引入了更多概念,所以我们希望智能指针这种东西来自动化管理所有资源。但不可否认,很多算法直接使用更底层的引用能提高效率。个人认为:结构组织需要智能指针,算法内部可以使用引用。

    错误处理

    为了处理异常情况,rust通过Result<T,E>定义了基础的处理框架。

    fn f1()->Result<(),Error>{
        File::open("file")?; //?自动返回错误
        OK(()) //正常情况
    }
    
    //main()函数怎么处理?
    fn main()->Result<(),Error>{}
    
    pub trait Termination{ //返回类型须支持该特征
        fn report(self) -> i32;
    }
    impl Termination for (){
        fn report(self) ->i32{
            ExitCode::SUCCESS.report()
        }
    }
    impl<E: fmt::Debug> Termination for Result<(),E>{
        fn report(self)->i32{
            match self{
                Ok(())=>().report(),
                Err(err)=>{
                    eprintln!("Error:{:?}",err);
                    ExitCode::FAILURE.report()
                }
            }
        }
    }
    

    交叉编译

    rustup target add wasm-wasi #编译目标为wasm(网页汇编)
    cargo build --target=wasm-wasi
    

    智能指针

    解引用:

    1. 当调用成员函数时,编译器会自动调用解引用,这样&T => &U => &K自动转换类型,直到找到该成员函数。
    // *p=>*(p.deref())
    // =>*(&Self)
    // =>Self
    pub trait Deref {
    type Target: ?Sized;
    fn deref(&self) -> &Self::Target;
    }
    
    // *p=>*(p.deref_mut())
    // =>*(&mut Self)
    // =>mut Self
    pub trait DerefMut: Deref {
    fn deref_mut(&mut self) -> &mut Self::Target;
    }
    

    常用智能指针(管理堆内存上的数据):

    拥有所有权:

    1. Box<T> =>T 堆上变量,对应普通栈上变量
    2. Vec<T> =>[T] 序列
    3. String == Vec<u8> =>[u8] 满足utf8编码

    共享所有权(模拟引用):

    1. Rc<T> =>T 共享,智能化的&,采用引用计数技术
      1. Rc<T>::clone() 产生镜像,并增加计数
      2. Rc<RefCell<T>> 模拟&mut,内部元素可写
    2. Arc<T> 多线程共享

    无所有权:

    1. Weak<T> 弱引用

    特殊用途:

    1. Cell<T> 内部可写
    2. RefCell<T> 内部可写
    3. Cow<T> Clone-on-Write
    4. Pin<T> 防止自引用结构移动

    产生循环引用的条件(如:Rc<RefCell<T>>):

    1. 使用引用计数指针
    2. 内部可变性

    设计递归型数据结构例子:

    //递归型数据结构,需要使用引用
    //引用是间接结构,否则该递归数据结构有无限大小
    //其次,引用需要明确初始化,引用递归自身导致无法初始化
    //因此,需要Option来定义没引用的情况
    //Node(Node) :无限大小
    //=> Node(Box<Node>) :不能初始化
    //=> Node(Option<Box<Node>>) :ok
    
    struct Node<T>{
        next: Option<Box<Self>>,
        data:T,
    }
    

    有些数据结构,一个节点有多个被引用的关系,比如双向链表,这时只能用Option<Rc<RefCell<T>>>这套方案,但存在循环引用的风险,程序员需要建立一套不产生循环引用的程序逻辑,如正向强引用,反向弱引用。

    编译器会对自引用(自己的成员引用自己,或另一个成员)的情况进行检查,因为违反了移动语义。

    闭包

    pub trait FnOnce<Args> {
    type Output;
    extern "rust-call" fn call_once(self, args: Args) -> Self::Output;
    }
    
    pub trait FnMut<Args> : FnOnce<Args> {
    extern "rust-call" fn call_mut(&mut self, args: Args) -> Self::Output;
    }
    
    pub trait Fn<Args> : FnMut<Args> {
    extern "rust-call" fn call(&self, args: Args) -> Self::Output;
    }
    

    动态分派和静态分派

    静态分派:泛型

    动态反派:特征对象(指针)

    特殊类型

    // 只读转可写引用:
    #[lang = "unsafe_cell"]
    #[stable(feature = "rust1", since = "1.0.0")]
    pub struct UnsafeCell<T: ?Sized> {
    value: T,
    }
    
    // 假类型(占位类型)
    #[lang = "phantom_data"]
    #[stable(feature = "rust1", since = "1.0.0")]
    pub struct PhantomData<T:?Sized>;
    
    // 内存分配器
    #[derive(Copy, Clone, Default, Debug)]
    pub struct Heap;
    

    成员方法

    struct A;
    
    impl A{
        fn f1(self){}
        fn f2(this:Self){}
    
        fn f3()->Self{A}
    }
    
    trait X{fn fx(self);}
    impl X for A{fn fx(self){}}
    
    //Self 表示实现的具体类型,本例为A
    //self = self:Self
    //&self = self:&Self
    //&mut self = self:&mut Self
    let a = A;
    a.f1(); //ok 可以用.调用成员方法
    a.f2(); //error 不可以,因为第一个参数名字不是self,虽然是Self类型
    A::f2(a); //ok
    
    let a=A::f3(); //无参数构造函数的形式,名字随意
    
    impl i32{} //error ,i32不是当前项目开发的,不能添加默认特征的实现,但可以指定一个特征来扩展i32
    impl X for i32{} //ok
    
    

    规则: 特征和实现类型,必须有一个是在当前项目,也就是说不能对外部类型已经实现的特征进行实现。也就是说,库开发者应该提供完整的实现。

    规则: 用小数点.调用成员函数时,编译器会自动转换类型为self的类型(只要可能)。

    但是要注意,&self -> self是不允许的,因为引用不能移动指向的数据。可以实现对应&T的相同接口来解决这个问题。

    let a = &A;
    a.fx(); //error 相当于调用fx(*a)
    impl X for &A{
        fn fx(self){} //这里的self = self:&A
    }
    a.fx(); //ok
    

    容器、迭代器、生成器

    容器 说明
    Vec 序列
    VecDeque 双向队列
    LinkedList 双向链表
    HashMap 哈希表
    BTreeMap B树表
    HashSet 哈希集
    BTreeSet B树集
    BinaryHeap 二叉堆
    trait Iterator {
    type Item;
    fn next(&mut self) -> Option<Self::Item>;
    ...
    }
    
    //for 循环实际使用的迭代器
    trait IntoIterator {
    type Item;
    type IntoIter: Iterator<Item=Self::Item>;
    fn into_iter(self) -> Self::IntoIter;
    }
    

    容器生成三种迭代器:

    1. ·iter() 创造一个Item是&T类型的迭代器;
    2. ·iter_mut() 创造一个Item是&mut T类型的迭代器;
    3. ·into_iter() 根据调用的类型来创造对应的元素类型的迭代器。
      1. T => R 容器为T,返回元素为R,即move
      2. &T => &R
      3. &mut T=> &mut R

    适配器(运算完毕返回迭代器):

    生成器(实验性):

    let mut g=||{loop{yield 1;}};
    let mut f = ||{ match Pin::new(&mut g).resume(){
        GeneratorState::Yielded(v)=>println!("{}", v),
        GeneratorState::Complete(_)=>{},
    };
    f(); //print 1
    f(); //print 1
    

    类型转换

    pub trait AsRef<T: ?Sized> {
    fn as_ref(&self) -> &T;
    }
    
    pub trait AsMut<T: ?Sized> {
    fn as_mut(&mut self) -> &mut T;
    }
    
    //要求hash不变
    pub trait Borrow<Borrowed: ?Sized> {
    fn borrow(&self) -> &Borrowed;
    }
    
    pub trait From<T> {
    fn from(T) -> Self;
    }
    
    //标准库已经有默认实现,即调用T::from
    pub trait Into<T> {
    fn into(self) -> T;
    }
    
    //克隆后转换
    pub trait ToOwned {
        type Owned: Borrow<Self>;
        fn to_owned(&self) -> Self::Owned;
    
        fn clone_into(&self, target: &mut Self::Owned) { ... }
    }
    
    //克隆写入
    pub enum Cow<'a, B>
    where
    B: 'a + ToOwned + ?Sized,
    {
    Borrowed(&'a B),
    Owned(<B as ToOwned>::Owned),
    }
    
    pub trait ToString {
    fn to_string(&self) -> String;
    }
    
    pub trait FromStr {
    type Err;
    fn from_str(s: &str) -> Result<Self, Self::Err>;
    }
    
    
    

    运算符重载

    trait Add<RHS = Self> {
    type Output;
    fn add(self, rhs: RHS) -> Self::Output;
    }
    
    

    I/O 操作

    平台相关字符串:

    • OsString
      • PathBuf
    • OsStr
      • Path

    文件读写:

    • File
    • Read
      • BufReader
    • Write

    标准输入输出:

    • Stdin
      • std::io::stdin()
    • Stdout
      • std::io::stdout()
    • std::env::args()
    • std::process::exit()

    反射

    std::any

    多任务编程

    启动新线程框架代码:

    use std::thread;
    
    let child = thread::spawn(move ||{});
    
    child.join(); //等待子线程结束
    

    数据竞争三个条件:

    1. 数据共享
    2. 数据修改
    3. 没有同步( rust 编译器保证同步)

    数据同步框架:

    • Sync 访问安全
      1. rust引用机制保证了数据访问基本是安全的
      2. 内部可变的类型除外(用了不安全代码)
      3. lock()的内部可变类型也是安全的
    • Send 移动安全
      1. 没有引用成员的类型;
      2. 泛型元素T是Send的;
      3. 或被lock()包装的。

    多线程下的数据总结:

    1. T :移动(或复制),因而无法共享(也就是只能给一个线程使用)
    2. &T
      1. 指向局部变量,生命周期报错
      2. 指向static T,ok
    3. &mut T
      1. 同理
      2. 指向static mut T, 不安全报错
    4. Box<T>:普通智能指针没有共享功能,等价T
    5. Rc<T>:普通共享指针没有Send特征,技术实现使用了内部可变,但没有加线程锁进行安全处理
    6. Arc<T> 提供了线程安全的共享指针
    7. Mutex<T>提供了线程安全的可写能力。
      1. RwLock
      2. AtomicIsize
    use std::sync::{Arc,Mutex};
    use std::thread;
    const COUNT:u32=1000000;
    
    let a = Arc::new(Mutex::new(123));//线程安全版共享且内部可变
    // 1
    let c = a.clone();
    let child1 = thread::spawn(move ||{for _ in 0..COUNT {*c.lock().unwrap()+=2;}});
    // 2
    let c = a.clone();
    let child2 = thread::spawn(move ||{for _ in 0..COUNT {*c.lock().unwrap()-=1;}});
    
    // 多任务同步
    child1.join().ok();
    child2.join().ok();
    println!("final:{:?}", a);//1000123
    

    模式匹配

    模式匹配我之前没什么接触,所以感觉挺有意思的(所以有了这一节)。

    在rust中,let,函数参数,for循环,if let,while let,match等位置实际上是一个模式匹配的过程,而不是其他语言中普通的定义变量。

    let x = 1; //x 这个位置是一个模式匹配的过程
    

    模式匹配会有两种结果,一种是不匹配,一种是匹配。一个模式匹配位置,要求不能存在不匹配的情况,这种叫必然匹配“irrefutable”,用于定义。否则,用于分支判断。

    很明显定义变量必然是要求匹配的,如let,函数参数,for循环。而用于分支判断的是if let,wdhile let,match。

    那为什么要用模式匹配来定义变量?因为很灵活,看一下它的表达能力:

    1. 普通类型的匹配:x --> T
    2. 解构引用:&x --> &T 得 x=T
    3. 解构数组:[_,_,x,_,_] --> [T;5] 得 x= 第三个元素
    4. 解构元组:(..,x) --> (1,2,"x") 得 x= 第三个成员
    5. 解构结构:T{0:_,1:x} --> T(1,"x") 得 x= 第二个成员

    通过与目标类型差不多的写法,对复杂类型进行解构,相对直观。

    另一方面,用于判断的模式匹配可以针对动态的内容,而不只是类型来进行匹配,如:

    1. 值范围匹配:x@1...10 --> 7 得 x=7
    2. 切片属于值匹配:x@[2,_] --> &[1,2,3][1..3] 得 x=&[2,3]
    //演示代码
    let [_,_,x,_,_] = [1;5];
    let (..,x) = (1,2,'y');
    let hi = &['h','e','l','l','o'][2..4];
    
    struct Ax(i32,char);
    let Ax{1:_,0:x} = Ax(1,'x');
    if let x@['l',_]=hi {println!("{:?}", x)};
    if let x@1...10 = 7 {println!("{}",x)};
    
  • 相关阅读:
    python网络编程--RabbitMQ
    python网络编程-同步IO和异步IO,阻塞IO和非阻塞IO
    python网络编程-SelectPollEpoll异步IO
    python网络编程--事件驱动模型
    python网络编程-协程(协程说明,greenlet,gevent)
    python网络编程--进程池
    python网络编程-进程锁
    python网络编程-进程间数据通信(Queue,Pipe ,managers)
    html之ul标签
    html之label标签
  • 原文地址:https://www.cnblogs.com/Nobel/p/12005842.html
Copyright © 2011-2022 走看看