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语言中的字符串,本质就是一个\0结束的字符串序列而已,并不包含什么大小字段,也不可能要求所有字符串都要带一个大小的字段。

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

特征对象的指针也是如此,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)};

rust 高级话题的更多相关文章

  1. 转:如何学习SQL(第四部分:DBMS扩展功能与SQL高级话题)

    转自:http://blog.163.com/mig3719@126/blog/static/285720652010950102575/ 9. DBMS提供的扩展功能 掌握了基本的关系模型原理和DB ...

  2. QuantLib 金融计算——高级话题之模拟跳扩散过程

    目录 QuantLib 金融计算--高级话题之模拟跳扩散过程 跳扩散过程 模拟算法 面临的问题 "脏"的方法 "干净"的方法 实现 示例 参考文献 如果未做特别 ...

  3. Spring高级话题-@Enable***注解的工作原理

    出自:http://blog.csdn.net/qq_26525215 @EnableAspectJAutoProxy @EnableAspectJAutoProxy注解 激活Aspect自动代理 & ...

  4. C#中的线程(四)高级话题

    C#中的线程(四)高级话题   Keywords:C# 线程Source:http://www.albahari.com/threading/Author: Joe AlbahariTranslato ...

  5. Dynamics CRM图表高级话题:创建跨实体的图表

    关注本人微信和易信公众号: 微软动态CRM专家罗勇 ,回复147或者20150728可方便获取本文,同时可以在第一时间得到我发布的最新的博文信息,follow me! 制作图表你会发现,在界面上只能选 ...

  6. Hawk 6. 高级话题:子流程系统

    子流程的定义 当流程设计的越来越复杂,越来越长时,就难以进行管理了.因此,采用模块化的设计才会更加合理.本节我们介绍子流程的原理和使用. 所谓子流程,就是能先构造出一个流程,然后被其他流程调用.被调用 ...

  7. 《Python 学习手册4th》 第十九章 函数的高级话题

    ''' 时间: 9月5日 - 9月30日 要求: 1. 书本内容总结归纳,整理在博客园笔记上传 2. 完成所有课后习题 注:“#” 后加的是备注内容 (每天看42页内容,可以保证月底看完此书) “重点 ...

  8. Spring Boot实战笔记(九)-- Spring高级话题(组合注解与元注解)

    一.组合注解与元注解 从Spring 2开始,为了响应JDK 1.5推出的注解功能,Spring开始大量加入注解来替代xml配置.Spring的注解主要用来配置注入Bean,切面相关配置(@Trans ...

  9. Spring Boot实战笔记(八)-- Spring高级话题(条件注解@Conditional)

    一.条件注解@Conditional 在之前的学习中,通过活动的profile,我们可以获得不同的Bean.Spring4提供了一个更通用的基于条件的Bean的创建,即使用@Conditional注解 ...

随机推荐

  1. javaWeb核心技术第十四篇之easyui

    网站是分为网站的前台和网站的后台. 前台--给用户看的 例如:商城 后台--给管理员看的 例如:商城后台 目的:用来添加维护数据 BootStrap:jsp 页面显示,效果好,美观,适合作为用户界面. ...

  2. Inherit from the Business Class Library Class 继承自Business类(EF)

    In this lesson, you will learn how to implement business classes for your application using the Busi ...

  3. Cesium专栏-空间分析之坡度分析(附源码下载)

    Cesium Cesium 是一款面向三维地球和地图的,世界级的JavaScript开源产品.它提供了基于JavaScript语言的开发包,方便用户快速搭建一款零插件的虚拟地球Web应用,并在性能,精 ...

  4. JS 简介

    JS 简介 JavaScript 是世界上最流行的编程语言. 这门语言可用于 HTML 和 web,更可广泛用于服务器.PC.笔记本电脑.平板电脑和智能手机等设备. avaScript 是脚本语言 J ...

  5. Angular回顾(1)

    一.前端基础 HTML:超文本标记语言,文档排版. DOM:文档对象模型. JS: 脚本语言,解释执行:动态操作DOM,服务器交互,用户交互. CSS:层叠样式表. 二.Angular前提 TypeS ...

  6. Django—使用后台管理Models

    后台的配置 1.创建后台管理员 [root@localhost study_django]# python manage.py createsuperuser [root@localhost stud ...

  7. Scrapy的Spider类和CrawlSpider类

    Scrapy shell 用来调试Scrapy 项目代码的 命令行工具,启动的时候预定义了Scrapy的一些对象 设置 shell Scrapy 的shell是基于运行环境中的python 解释器sh ...

  8. MySQL 的 4 种隔离级别,你了解么?

    1.什么是事务 事务是应用程序中一系列严密的操作,所有操作必须成功完成,否则在每个操作中所作的所有更改都会被撤消.也就是事务具有原子性,一个事务中的一系列的操作要么全部成功,要么一个都不做.事务的结束 ...

  9. request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+request.getContextPath()+"/"

    String path = request.getContextPath(); String basePath = request.getScheme()+"://"+reques ...

  10. apache配置文件说明及一些指令

    httpd命令和apachectl命令 [root@localhost ~]# httpd -h Usage: httpd [-D name] [-d directory] [-f file] [-C ...