- 沒有內存安全保證:Unsafe Rust谨究。
- 和普通Rust一樣恩袱,提供了額外能力。
- 存在原因:
- 靜態(tài)分析時保守的:unsafe rust 我知道自己在做什么 并承擔風險胶哲。
- 計算機本身就是不安全的畔塔,需要進行底層編程。
Unsafe超能力:
用unsafe關鍵字創(chuàng)建一個塊兒:
- 解引用原始指針
- 調用unsafe 函數(shù)或者方法
- 訪問或者修改常量變量
- 實現(xiàn)unsafe trait
注意:
- 沒有關閉rust安全特性
- 錯誤必須留在unsafe里
- 盡可能隔離unsafe代碼鸯屿,對外提供安全api
解引用原始指針
- 原始指針:
- 可變的:*mut T
- 不可變的:*const T
- 與引用不同:
- 允許通過同時具有不可變和可變指針或多個指向同一位置的可變指針來忽略借用規(guī)則澈吨。
- 無法保證指向合理內存
- 允許為null
- 不實現(xiàn)任何自動清理
-
放棄安全:換取性能或者一些其他語言和接口的硬件能力。
image.png - 為什么要用碾盟?
- 與C語言進行接口
- 構造借用檢查器無法理解的安全抽象
調用unsafe函數(shù)或者方法
- 定義前加上unsafe棚辽,調用前需要手動滿足一些條件
- unsafe塊里調用
創(chuàng)建unsafe代碼的安全抽象
函數(shù)包含unsafe并不一定將整個函數(shù)標記為unsafe。
將unsafe代碼塊包裹在函數(shù)中是常見抽象冰肴。
使用extern函數(shù)調用代碼
- extern默認就是不安全的
- 簡化創(chuàng)建和使用外部函數(shù)接口的過程屈藐。
- 允許其他編程語言調用本語言函數(shù)。
- 應用二進制接口熙尉,匯編層面接口联逻。
-
C ABI最常見ABI遵循C語言:
從其他語言調用Rust接口
- 可以使用extern創(chuàng)建接口,其他語言可以調用Rust函數(shù)
- 在fn前添加extern并制定ABI
-
需要添加#[no_mangle]注解检痰,避免Rust編譯時改變其名稱
訪問或者修改一個可變靜態(tài)變量
- 支持全局變量:在rust里叫做靜態(tài)變量
靜態(tài)變量:
- 靜態(tài)變量和常量類似
- SCREAMING_SNAKE_CASE
- 必須標注類型
- 不需要標明聲明周期
- 訪問不可靜態(tài)變量是安全的
常量和不可變靜態(tài)變量的區(qū)別
-
靜態(tài)變量:
- 有固定的內存地址包归,使用他的值總會放問同樣的數(shù)據(jù)。
-
可變的:訪問和修改是unsafe的:
常量:允許使用他們的時候對數(shù)據(jù)進行復制铅歼。
實現(xiàn)不安全的trait
- 當某個trait中存在一個方法擁有編譯器無法校驗的不安全因素的時候公壤,就稱這個trait是不安全的。
-
在trait前加unsafe:
何時使用unsafe代碼
- 編譯器無法保證內存安全
- 有充分理由
- 顯示標記unsafe椎椰,快速定位問題
高級Trait
在Trait定義中使用關聯(lián)類型來指定占位類型
- 關聯(lián)類型:Trait中類型的占位符厦幅,用在方法簽名中
-
可以定義出包含某些類型的Trait,而在實現(xiàn)前無需知道這些類型是什么:
-
和泛型的區(qū)別
泛型:
- 實現(xiàn)Trait時需要標注類型
-
可以實現(xiàn)多次Trait(不同泛型參數(shù))
關聯(lián)類型:
- 無需標注類型
-
無法為單個類型多次實現(xiàn)某個Trait
默認泛型參數(shù)和運算符的重載
- 指定默認泛型具體類型
- <T = u32>
- 常用于運算符重載
- Rust不允許創(chuàng)建自己的運算符和重載任意運算符
-
但是可以重載std:ops列舉出來的一部分運算符
原來:
運用場景
- 擴展一個類型而不破壞現(xiàn)有代碼
- 允許在大部分用戶都不需要的特定場景下進行自定義
完全限定語法(fully qualified syntax)
指定對應的triat慨飘,然后傳入引用确憨。
但是上面的例子得以實現(xiàn)時因為fly的參數(shù)是self,但是如果沒有參數(shù)瓤的,怎么能調用其他trait的方法休弃?
這個時候就用到了完全限定語法: <Type as Trait>::function(xx, xxx);
使用supertrait來調用其他trait
- 被依賴的trait也被實現(xiàn)
-
被依賴的trait就是當前trait的supertriat
使用newtype模式在外部類型上實現(xiàn)外部trait
- 孤兒規(guī)則:定義在本地包,才能實現(xiàn)trait
-
利用newtype繞過該規(guī)則:tuple struct
trait和vec都在外部圈膏,但是我們要為vec實現(xiàn)trait塔猾,實際上就是創(chuàng)建了一個新的struct來包了一層對應的類型。
高級類型
使用newtype模式實現(xiàn)類型安全和抽象
newtype可以:
- 用來靜態(tài)保證各種值不被混淆
- 為類型的某些細節(jié)提供抽象能力
- 通過輕量級的封裝來隱藏內部實現(xiàn)細節(jié)
使用類型別名來創(chuàng)建同義詞
- rust提供了類型別名功能:type關鍵字稽坤,并非獨立類型桥帆。
-
主要用來減少代碼字符重復医增。
Never類型
- 有一個名為!的特殊類型:
- 它沒有任何值老虫,行話成為空類型
-
不返回值的函數(shù)充當返回類型
不寫返回值不代表沒有返回值,所以報錯茫多。
Never類型可以強制轉化為其他類型:
panic!也是never祈匙。
無限循環(huán)也是never。
動態(tài)大小和Sized Trait
- Rust需要在編譯時天揖,確定為一個特定類型的值分配多少空間夺欲。
- 動態(tài)大小類型(DST):編寫代碼是使用只有在運行才能確定大小的值。
-
str是動態(tài)大小類型(不是&str):運行時才能確定長度:
Rust使用動態(tài)大小類型的通用方式
- 附帶一些元數(shù)據(jù)來存儲動態(tài)信息的大小今膊。
- 使用動態(tài)大小類型時總會把它的值放在某種指針后邊
另一種動態(tài)大小的類型:Trait
- 每個trait都是一個動態(tài)大小的類型些阅,通過名稱進行引用。
-
為了將trait用作trait對象斑唬,必須把它放置在某種指針之后市埋。
Sized Trait
來確定一個類型的大小是否在編譯時已知。
- 編譯時計算出大小的類型會自動實現(xiàn)該trait
-
Rust還會為每一個泛型函數(shù)隱式的添加sized約束
泛型函數(shù)只能被用于編譯時已經(jīng)知道大小的類型恕刘,但是特殊語法可以解除這一限制:
?Sized Trait約束
高級函數(shù)和閉包
函數(shù)指針
- 可以將函數(shù)傳遞給其他函數(shù)
- 函數(shù)在傳遞的過程中被強制轉化為fn類型
-
fn類型就是函數(shù)指針(function pointer)
函數(shù)指針和閉包的不同
- fn是一個類型缤谎,不是一個trait:可以指定fn為參數(shù),而不用以Fn Trait為約束的泛型參數(shù)
- 函數(shù)指針實現(xiàn)了全部3種閉包的trait(Fn FnMut FnOnce):總是可以把一個函數(shù)指針作為參數(shù)傳遞給一個接收閉包的函數(shù)
- 所以褐着,傾向于搭配閉包Trait的泛型來編寫函數(shù):可以同時接收閉包和普通函數(shù)
-
某些場景只想接收fn 而不想接收閉包:和其他語言交互
返回閉包
-
閉包使用trait表示坷澡,無法直接返回一個閉包,可以將實現(xiàn)了該trait的具體類型作為返回值:
宏
- 相關特性的集合稱謂
- 使用macro_rules!來構建聲明宏
- 3種過程宏
- 定義#[derive]宏含蓉,用于struct或者enum频敛,可為其指定隨derive屬性添加的代碼
- 類似屬性的宏,在任何條目上添加自定義屬性
- 類似函數(shù)的宏馅扣,看起來像函數(shù)調用斟赚,對其指定為參數(shù)的token進行操作
函數(shù)和宏的差別
- 本質上,宏是用來編寫可以生成其他代碼的代碼(元編程岂嗓,metaprogramming)
- 函數(shù)在定義簽名時汁展,必須聲明參數(shù)的個數(shù)和類型,宏可處理可變參數(shù)
- 編譯器會在解釋代碼前厌殉,把宏展開
- 宏的定義比函數(shù)復雜得多食绿,難以閱讀、理解公罕、維護
- 在某個文件調用宏時器紧,必須提前定義宏或將宏引入當前作用域
- 函數(shù)可以在任何位置定義并在任何位置調用
macro_rules! 聲明宏
- Rust最常見的宏形式:聲明宏
- 類似match的模式匹配
- 需要使用macro_rules!
例子:
基于屬性來生成代碼的過程宏
- 像函數(shù):
- 接受一段rust代碼
- 生成另外一些rust代碼為結果
- 三種過程宏:
- 自定義派生
- 屬性宏
- 函數(shù)宏
- 創(chuàng)建過程宏時:
-
宏定義必須單獨放在他們自己的包中,并使用特殊的包類型
-
自定義derive宏
- 需求:
- 創(chuàng)建一個hello_macro包楼眷,定義一個擁有關聯(lián)函數(shù)hello_macro的HelloMacro trait
- 我們提供一個能夠自動實現(xiàn)trait的過程宏
- 在他們上標明#[derive(HelloMacro)]铲汪,進而得到hello_macro的默認實現(xiàn)熊尉。
一個trait往往需要默認實現(xiàn),如果我有9個struct都需要使用一個trait掌腰,那么我的默認實現(xiàn)可能就要寫9次狰住,而往往默認實現(xiàn)都非常的相似,我們不想寫這么多次齿梁,那么怎么辦催植?
首先我們先創(chuàng)建了一個工作空間,底下有3個項目:
- hello_macro
- hello_macro_derive (過程宏)
-
hell_macro的使用方:Pancakes
hello_macro:
Pancakes希望最終能寫成:
如果沒有hello_macro_derive勺择,它只能寫成:
就得寫默認實現(xiàn)创南。
那么hello_macro_derive怎么實現(xiàn):
他的依賴:
- syn:rust代碼變成ast
-
quote:將ast還原為字符串
固定寫法:
主要是impl_hello_macro:
stringify!:將表達式變成字面字符串:比如stringify!(1 + 2) 輸出 "1+2";
類似屬性的宏
- 屬性宏和自定義derive宏類似
- 允許創(chuàng)建新的屬性
- 但不是為derive屬性生成代碼
-
屬性宏更靈活:運用在任何條目
類似函數(shù)的宏
- 類似函數(shù)調用的宏,但是比普通函數(shù)更加靈活
- 可以接收TokenStream作為參數(shù)
-
可以操作TokenStream