目录程序员
每一种语言都有它比较隐秘的点。rust也不例外。算法
没法静态肯定大小或对齐的类型。编程
特征包含vtable,经过vtable访问成员。切片是数组或Vec的一个视图。数组
rust是一个快速变化的语言和编译系统,建议用最新版,不然可能出现兼容性问题而没法经过编译。安全
rustup 管理工具链的版本,分别有三个通道:nightly、beta、stable。如上所述,建议同时安装nightly版本,保持最新状态。bash
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 |
元组结构 | 无 | - |
新类型 | - | 1 |
类单元结构 | - | 0 |
枚举和结构是不一样的,枚举是一个集合(相似c语言种的联合union,变量的枚举成员可选,而不是全体成员),而结构是一个记录(成员一定存在)。闭包
做为一个描述力比较强的语法对象,结构不少时候都在模拟成基础类型,可是更多时候是具有和基础类型不一样的特征,而须要由用户来定制它的行为。框架
基础数据类型,引用类1(引用&T,字符串引用&str,切片引用&[T],特征对象&T:strait,原生指针*T),元组(T2),数组[T 2;size],函数指针fn()默认都是copy;而结构、枚举和大部分标准库定义的类型(智能指针,String等)默认是move。
注意:
#[derive(Copy,Clone,Debug)] //模拟基础数据类型的自动复制行为,Debug 特征是为了打印输出 struct A; let a = A; let b = a;//自动copy 而不是move println!("{:?}", a);//ok
rust的基本类型分类能够如此安排:
特征是一个动态大小类型,它的对象大小根据实现它的实体类型而定。这一点和别的语言中的接口有着本质的不一样。rust定义的对象,必须在定义的时候即获知大小,不然编译错误。
可是,泛型定义除外,由于泛型的实际定义是延迟到使用该泛型时,即给出具体类型的时候。
或者,能够用间接的方式,如引用特征对象。
trait s{} fn foo(a:impl s)->impl s{a} //这里impl s相等于泛型T:s,属于泛型技术,根据具体类型单态化
rust的核心创新:
必须总体的理解他们之间的关系。
借用(引用)是一种怎样的状态?
例子:
操做 | 旧读 | 旧写 | 新读 | 新写 | 新副本 |
---|---|---|---|---|---|
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
引用并无论理对象生命周期,由于它没有对象的全部权。引用须要断定的是引用的有效性,即对象生命周期长于引用自己便可。
当须要管理生命周期时,不该该使用引用,而应该用智能指针。
通常而言,咱们不喜欢引用,由于引用引入了更多概念,因此咱们但愿智能指针这种东西来自动化管理全部资源。但不能否认,不少算法直接使用更底层的引用能提升效率。我的认为:结构组织须要智能指针,算法内部可使用引用。
为了处理异常状况,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
解引用:
&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; }
经常使用智能指针(管理堆内存上的数据):
拥有全部权:
Box<T>
=>T 堆上变量,对应普通栈上变量Vec<T>
=>[T] 序列String == Vec<u8> =>[u8]
知足utf8编码共享全部权(模拟引用):
Rc<T>
=>T 共享,智能化的&,采用引用计数技术
Rc<T>::clone()
产生镜像,并增长计数Rc<RefCell<T>>
模拟&mut,内部元素可写Arc<T>
多线程共享无全部权:
Weak<T>
弱引用特殊用途:
Cell<T>
内部可写RefCell<T>
内部可写Cow<T>
Clone-on-WritePin<T>
防止自引用结构移动产生循环引用的条件(如:Rc<RefCell<T>>
):
设计递归型数据结构例子:
//递归型数据结构,须要使用引用 //引用是间接结构,不然该递归数据结构有无限大小 //其次,引用须要明确初始化,引用递归自身致使没法初始化 //所以,须要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; }
容器生成三种迭代器:
·iter()
创造一个Item是&T类型的迭代器;·iter_mut()
创造一个Item是&mut T类型的迭代器;·into_iter()
根据调用的类型来创造对应的元素类型的迭代器。
适配器(运算完毕返回迭代器):
生成器(实验性):
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; }
平台相关字符串:
文件读写:
标准输入输出:
std::any
启动新线程框架代码:
use std::thread; let child = thread::spawn(move ||{}); child.join(); //等待子线程结束
数据竞争三个条件:
数据同步框架:
lock()
的内部可变类型也是安全的lock()
包装的。多线程下的数据总结:
T
:移动(或复制),于是没法共享(也就是只能给一个线程使用)&T
static T
,ok&mut T
static mut T
, 不安全报错Box<T>
:普通智能指针没有共享功能,等价T
Rc<T>
:普通共享指针没有Send特征,技术实现使用了内部可变,但没有加线程锁进行安全处理Arc<T>
提供了线程安全的共享指针Mutex<T>
提供了线程安全的可写能力。
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。
那为何要用模式匹配来定义变量?由于很灵活,看一下它的表达能力:
x --> T
&x --> &T
得 x=T[_,_,x,_,_] --> [T;5]
得 x= 第三个元素(..,x) --> (1,2,"x")
得 x= 第三个成员T{0:_,1:x} --> T(1,"x")
得 x= 第二个成员经过与目标类型差很少的写法,对复杂类型进行解构,相对直观。
另外一方面,用于判断的模式匹配能够针对动态的内容,而不仅是类型来进行匹配,如:
//演示代码 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)};