rust 高級話題

rust高級話題

前言

每一種語言都有它比較隱祕的點。rust也不例外。算法

動態大小類型DST

沒法靜態肯定大小或對齊的類型。編程

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

特徵包含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。

注意:

  1. 引用只是複製指針自己,且沒有數據的全部權;由於可變引用惟一,在同一做用域中沒法複製,此時是移動語義的,但能夠經過函數參數複製傳遞,此時等於在此位置從新建立該可變引用。
  2. 元素必須爲可複製的
#[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. 指針

特徵對象

特徵是一個動態大小類型,它的對象大小根據實現它的實體類型而定。這一點和別的語言中的接口有着本質的不一樣。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)};
相關文章
相關標籤/搜索