目錄
- 一、為了類型安全和抽象而使用 newtype 模式
- 二、使用類型別名創建類型同義詞
- 2.1 使用type關鍵賦予現有類型一個別名
- 2.2 減少重復
- 2.3 與Result<T, E>結合使用
- 2.4 從不返回的 never type
- 三、高級函數和閉包
- 3.1 函數指針
- 3.2 返回閉包
- 四、宏
- 4.1 宏和函數的區別
- 4.2 macro_rules! 的聲明宏
- 4.3 基于屬性生成代碼的過程宏
- 4.4 編寫自定義 derive 宏
一、為了類型安全和抽象而使用 newtype 模式
newtype
模式的應用可以用于確保靜態值不被混淆以及表示一個值的單元;newtype
模式的應用可以抽象掉一些類型的實現細節;- 例如封裝類型可以暴露出與直接使用其內部私有類型時所不同的公有 API,以便限制其功能;
newtype
模式也可以隱藏其內部的泛型類型;
二、使用類型別名創建類型同義詞
2.1 使用type關鍵賦予現有類型一個別名
fn main() {type Kilometers = i32;let x: i32 = 5;let y: Kilometers = 5;println!("x + y = {}", x + y);
}
- 代碼輸出:
x + y = 10
;
2.2 減少重復
- 類型別名的主要用途是減少重復,例如類型Box<dyn Fn() + Send + 'static>;
let f: Box<dyn Fn() + Send + 'static> = Box::new(|| println!("hi"));fn takes_long_type(f: Box<dyn Fn() + Send + 'static>) {}fn returns_long_type() -> Box<dyn Fn() + Send + 'static> {}
- 通過
type
關鍵字引入類型別名,則可以修改為
type Thunk = Box<dyn Fn() + Send + 'static>;let f: Thunk = Box::new(|| println!("hi"));fn takes_long_type(f: Thunk) {}fn returns_long_type() -> Thunk {}
2.3 與Result<T, E>結合使用
- 標準庫中的
std::io
模塊; - I/O 操作通常會返回一個
Result<T, E>
; - 標準庫中的
std::io::Error
結構體代表了所有可能的 I/O 錯誤; std::io
中大部分函數會返回Result<T, E>
;
use std::io::Error;
use std::fmt;pub trait Write {fn write(&mut self, buf: &[u8]) -> Result<usize, Error>;fn flush(&mut self) -> Result<(), Error>;fn write_all(&mut self, buf: &[u8]) -> Result<(), Error>;fn write_fmt(&mut self, fmt: fmt::Arguments) -> Result<(), Error>;
}
- 上述代碼出現了很多
Result<..., Error>
,因此,std::io
有別名聲明;
type Result<T> = std::result::Result<T, std::io::Error>;
- 由于位于
std::io
,可用的完全限定的別名是std::io::Result<T>
,即Result<T, E>
中 E 放入了std::io::Error
- 最后的效果如下
pub trait Write {fn write(&mut self, buf: &[u8]) -> Result<usize>;fn flush(&mut self) -> Result<()>;fn write_all(&mut self, buf: &[u8]) -> Result<()>;fn write_fmt(&mut self, fmt: fmt::Arguments) -> Result<()>;
}
2.4 從不返回的 never type
- Rust有一個
!
的特殊類型,它被稱為empty type
,更傾向于稱之為never type
; - 主要用于在函數從不返回的時候充當返回值;
- 從不返回的函數被稱為發散函數;
fn bar() -> ! {}
用途
- 有如下代碼
let guess: u32 = match guess.trim().parse() {Ok(num) => num,Err(_) => continue,
};
- 我們知道
match
分支必須返回相同的類型; - 如下代碼必無法通過編譯
let guess = match guess.trim().parse() {Ok(_) => 5,Err(_) => "hello",
}
- 上述代碼里的
guess
必須既是整型也是字符串,而 Rust 要求guess 只能是一個類型; - 所以continue 返回的值是
!
;
never type 的另一個用途是 panic!
三、高級函數和閉包
3.1 函數指針
- 可以向函數傳遞閉包,也可以向函數傳遞常規函數;
- 函數的類型是
fn
,它被稱為函數指針 ;
fn add_one(x: i32) -> i32 {x + 1
}fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 {f(arg) + f(arg)
}fn main() {let answer = do_twice(add_one, 5);println!("The answer is: {}", answer); //The answer is: 12
}
do_twice
函數中的f
被指定為一個接受一個i32 參數并返回 i32 的函數指針;- 就可以在
do_twice
函數體中調用該函數; fn
是一個類型而不是一個trait;- 直接指定 fn 作為參數;
- 而非聲明一個帶有 Fn 作為 trait bound 的泛型參數;
- 函數指針實現了所有三個閉包 trait:Fn、FnMut 和 FnOnce;
- 總是可以在調用期望閉包的函數時傳遞函數指針作為參數;
- 當與不存在閉包的外部代碼交互時,可以只期望接受 fn 而不接受閉包;
let list_of_numbers = vec![1, 2, 3];
let list_of_strings: Vec<String> = list_of_numbers.iter().map(|i| i.to_string()).collect();
- 上述代碼使用map函數將一個數字vector 轉換為一個字符串 vector;
- 也可以將函數作為 map 的參數來代替閉包;
let list_of_numbers = vec![1, 2, 3];
let list_of_strings: Vec<String> = list_of_numbers.iter().map(ToString::to_string).collect();
- 另一個實用的模式暴露了元組結構體和元組結構體枚舉成員的實現細節;
- 這些項使用 () 作為初始化語法(看起來就像函數調用);
- 同時它們確實被實現為返回由參數構造的實例的函數;
- 它們也被稱為實現了閉包 trait 的函數指針,并可以采用類似如下的方式調用;
enum Status {Value(u32),Stop,
}let list_of_statuses: Vec<Status> =(0u32..20).map(Status::Value).collect();
- 創建了
Status::Value
實例,它通過map用范圍的每一個u32 值調用 Status::Value 的初始化函數;
3.2 返回閉包
- 如下的代碼不能通過編譯
fn returns_closure() -> Fn(i32) -> i32 {|x| x + 1
}
- 使用 trait 對象解決
fn returns_closure() -> Box<dyn Fn(i32) -> i32> {Box::new(|x| x + 1)
}
四、宏
- 宏(Macro)指的是 Rust 中一組相關特性的集合:
- 使用
macro_rules!
聲明的(Declarative)宏,和三種過程(Procedural)宏:- 自定義
#[derive]
宏,用于結構體和枚舉上指定通過derive屬性添加的代碼; - 類似屬性宏,可用于任意項的自定義屬性;
- 類函數宏,看起來像函數調用,作用于作為參數傳遞的 token;
- 自定義
- 使用
4.1 宏和函數的區別
- 宏是一種為寫其他代碼而編寫的代碼,即所謂的元編程(metaprogramming);
- 一個函數標簽必須聲明函數參數個數和類型,宏能夠接受不同數量的參數;
- 在一個文件里調用宏之前必須定義,或將其引入作用域,函數則可以在任何地方定義和調用;
4.2 macro_rules! 的聲明宏
- 最常用的宏形式是 聲明宏(declarative macros),它允許我們編寫一些類似 Rust match 表達式的代碼 ;
- 使用
macro_rules!
定義宏; vec![1, 2, 3];
調用下面的宏 (簡化);
#[macro_export]
macro_rules! vec {( $( $x:expr ),* ) => {{let mut temp_vec = Vec::new();$(temp_vec.push($x);)*temp_vec}};
}
#[macro_export]
標注說明,只要將定義了宏的crate
引入作用域,宏就應當是可用的;- 沒有該標注的宏不能被引入作用域;
- 使用
macro_rules!
和宏名稱開始宏定義,且所定義的宏并不帶感嘆號,名字后跟大括號表示宏定義體; $x:expr
指匹配任何的Rust表達式并命名為$x
,后面的逗號表示傳入的逗號分隔符,后面的*
表示能匹配0個或多個;- 全部宏語法,參參閱:https://rustwiki.org/zh-CN/reference/macros.html
4.3 基于屬性生成代碼的過程宏
- 過程宏(procedural macros),更像函數(一種過程類型);
- 過程宏接收Rust代碼作為輸入,然后產生另一些代碼作為輸出;
- 還有一種類型的過程宏:
- 自定義派生;
- 屬性宏;
- 函數宏;
- 創建過程宏時
- 宏定義必須單獨放在它們自己的包中,并使用特殊的包類型;
use proc_macro;#[some_attribute]
pub fn some_name(input: TokenStream) -> TokenStream {
}
- some_attribute是過程宏的占位符;
- 定義過程宏的函數以一個
TokenStream
作為輸入并產生一個TokenStream
作為輸出; - 該
TokenStream
類型由包含在 Rust 中的proc_macro
crate定義,并表示令牌序列;
4.4 編寫自定義 derive 宏
- 創建
hello_macro
crate,定義一個擁有關聯函數HelloMacro的 trait 和關聯函數hello_macro; - 提供一個能自動實現trait的過程宏;
- 使用戶在它們的類型上標注
#[derive(HelloMacro)]
,進而得到hello_macro的默認實現;
實現
- 在一個全新目錄下(稱為工作空間)創建
Cargo.toml
文件,寫上[workspace]
就行了; - 再相同的目錄下輸入下面兩條指令,創建兩個crate;
cargo new hello_macro --lib
cargo new hello_macro_derive --lib
cargo new pancakes
hello_macro_derive\Cargo.toml
文件的內容如下
[package]
name = "hello_macro_derive"
version = "0.1.0"
edition = "2021"[lib]
proc-macro = true[dependencies]
syn = "2.0.68"
quote = "1.0"
Cargo.toml
文件內容如下
[workspace]members = ["hello_macro", "hello_macro_derive", "pancakes"]
pancakes/Cargo.toml
文件內容如下
[package]
name = "pancakes"
version = "0.1.0"
edition = "2021"[dependencies]
hello_macro = {path = "../hello_macro"}
hello_macro_derive = {path = "../hello_macro_derive"}
- 將過程宏放到
hello_macro_derive
里,其lib.rs內容如下
extern crate proc_macro;use crate::proc_macro::TokenStream;
use quote::quote;
use syn;#[proc_macro_derive(HelloMacro)]
pub fn hello_macro_derive(input: TokenStream) -> TokenStream {// 將 Rust 代碼解析為語法樹以便進行操作let ast = syn::parse(input).unwrap();// 構建 trait 實現impl_hello_macro(&ast)
}fn impl_hello_macro(ast: &syn::DeriveInput) -> TokenStream {let name = &ast.ident;let gen = quote! {impl HelloMacro for #name {fn hello_macro() {println!("Hello, Macro! My name is {}", stringify!(#name));}}};gen.into()
}
proc_macro
包提供了編譯器接口,從而可以讀取可操作的Rust代碼;syn
是把Rust代碼從字符串轉換為可供我們進一步操作的數據結構;quote
包能夠將syn產生的數據結構重新轉換為Rust代碼;- 函數
hello_macro_derive
負責解析TokenStream,函數內部的impl_hello_macro
負責轉換語法庫; - 效果是:用戶標注
#[derive(HelloMacro)]
后,hello_macro_derive 會被自動調用; - 詳細的看相關的文檔;
- hello_macro/src/lib.rs中的代碼為
pub trait HelloMacro{fn hello_macro();
}
- pancakes/src/main.rs的代碼為
use hello_macro::HelloMacro;
use hello_macro_derive::HelloMacro;#[derive(HelloMacro)]
struct Pancakes;fn main() {Pancakes::hello_macro();
}
- 運行的結果如下