国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

【跟小嘉學(xué) Rust 編程】十九、高級特性

這篇具有很好參考價值的文章主要介紹了【跟小嘉學(xué) Rust 編程】十九、高級特性。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

系列文章目錄

【跟小嘉學(xué) Rust 編程】一、Rust 編程基礎(chǔ)
【跟小嘉學(xué) Rust 編程】二、Rust 包管理工具使用
【跟小嘉學(xué) Rust 編程】三、Rust 的基本程序概念
【跟小嘉學(xué) Rust 編程】四、理解 Rust 的所有權(quán)概念
【跟小嘉學(xué) Rust 編程】五、使用結(jié)構(gòu)體關(guān)聯(lián)結(jié)構(gòu)化數(shù)據(jù)
【跟小嘉學(xué) Rust 編程】六、枚舉和模式匹配
【跟小嘉學(xué) Rust 編程】七、使用包(Packages)、單元包(Crates)和模塊(Module)來管理項目
【跟小嘉學(xué) Rust 編程】八、常見的集合
【跟小嘉學(xué) Rust 編程】九、錯誤處理(Error Handling)
【跟小嘉學(xué) Rust 編程】十一、編寫自動化測試
【跟小嘉學(xué) Rust 編程】十二、構(gòu)建一個命令行程序
【跟小嘉學(xué) Rust 編程】十三、函數(shù)式語言特性:迭代器和閉包
【跟小嘉學(xué) Rust 編程】十四、關(guān)于 Cargo 和 Crates.io
【跟小嘉學(xué) Rust 編程】十五、智能指針(Smart Point)
【跟小嘉學(xué) Rust 編程】十六、無畏并發(fā)(Fearless Concurrency)
【跟小嘉學(xué) Rust 編程】十七、面向?qū)ο笳Z言特性
【跟小嘉學(xué) Rust 編程】十八、模式匹配(Patterns and Matching)
【跟小嘉學(xué) Rust 編程】十九、高級特性

前言

到目前為止,我們已經(jīng)學(xué)習(xí)了 Rust 之中最常用的部分,本章節(jié)講解如下特性

  • 不安全 Rust:如何選擇退出 Rust 的某些保證,并負(fù)責(zé)手動維護(hù)這些保證;
  • 高級特征:關(guān)聯(lián)類型、默認(rèn)類型參數(shù)、完全限定語法、超特征以及特征相關(guān)的newtype模式
  • 高級類型:更多關(guān)于newtype模式、類型別名、never類型和動態(tài)大小類型的內(nèi)容高級函數(shù)和閉包:函數(shù)指針和返回閉包
  • 宏:定義在編譯時定義更多代碼的方法

主要教材參考 《The Rust Programming Language》


一、 不安全 Rust

1.1、不安全 Rust

到目前為止,我們討論的所有代碼在編譯時強制執(zhí)行了 Rust 的內(nèi)存安全保證,然而 Rust 內(nèi)部隱藏著另一種語言,它不強制執(zhí)行這些內(nèi)存安全保證,它被稱為不安全 Rust 和常規(guī) Rust 一樣工作,但賦予我們額外的能力。

不安全 Rust 之所以存在,是因為靜態(tài)分析本質(zhì)是保守的。當(dāng)編譯器試圖確定代碼是否支持這些保證時候,拒絕一些有效的程序比接受一些無效的程序要好。雖然代碼可能沒有問題,但如果 Rust 編譯器沒有足夠的信息來確定,它將拒絕代碼。在這些情況下,您可以使用不安全代碼告訴編譯器:“相信我,我知道我在做什么”。但是請注意,使用不安全的 Rust 的風(fēng)險由您自己承擔(dān),如果不正確地使用不安全的代碼,可能會由于內(nèi)存不安全而出現(xiàn)問題,例如空指針解引用。

Rust 具有不安全另一面的原因是底層計算機硬件本質(zhì)上是不安全的。如果 Rust 不允許你做不安全的操作,你就不能完成某些任務(wù)。 Rust 需要允許您進(jìn)行低級系統(tǒng)編程,例如直接與操作系統(tǒng)交互,甚至編寫自己的操作系統(tǒng)。處理低級系統(tǒng)編程是該語言的目標(biāo)之一,讓我們來探索一下不安全 Rust 可以做什么 以及如何做。

1.2、unsafe 關(guān)鍵字

要切換到不安全 Rust 使用 unsafe 關(guān)鍵字,然后啟動一個包含不安全代碼的新塊,你可以在不安全的 Rust 中執(zhí)行五個在安全 Rust 中無法執(zhí)行的操作,我們稱之為不安全的超能力。

  • 解引用裸指針
  • 調(diào)用不安全的函數(shù)或方法
  • 訪問或修改可變靜態(tài)變量
  • 實現(xiàn)不安全的Trait
  • 訪問聯(lián)合體的字段

1.2.1、解引用裸指針(Dereference a raw pointer)、

1.2.1.1、裸指針(raw pointer)

裸指針(raw pointer,又稱原生指針)在功能上跟引用類型,同時也需要顯式地注明可變性。但是又和引用有所不同,裸指針形式如: * const T*mut T,分別代表了不可變和可變。

* 操作符,可以用于解引用,但是裸指針 * const T 中,* 只是類型名稱的一部分,并沒有解引用的含義。

至此我們已經(jīng)學(xué)過三種類似指針的概念:引用、 智能指針、裸指針。與前兩者不同,裸指針可以繞過 Rust 的借用規(guī)則,可以同時擁有一個數(shù)據(jù)的可變、不可變指針,甚至可以擁有多個可變的指針,并不能保證指向合法的內(nèi)存,可以是null,沒有實現(xiàn)任何自動回收的(drop)

總之裸指針和 C指針非常像,它需要以犧牲安全性為前提,但是我們獲得了更好的性能,也可以跟其他語言和硬件打交道。

1.2.1.2、基于引用創(chuàng)建裸指針

范例:基于引用創(chuàng)建裸指針

let mut num = 5;

let r1 = &num as *const i32;
let r2 = &mut num as *mut i32;

as 關(guān)鍵字可以用于強制類型轉(zhuǎn)換,我們這里將引用 &mut / & mut num 強制轉(zhuǎn)換為 * const i32 / * mut i32

在這段代碼里面并沒有 unsafe 身影,因為創(chuàng)建裸指針是安全的行為,而解引用裸指針才是不安全的行為。

范例:解引用裸指針

fn main() {
    let mut num = 5;

    let r1 = &num as *const i32;

    unsafe {
        println!("r1 is: {}", *r1);
    }
}

1.2.1.3、內(nèi)存地址創(chuàng)建裸指針

我們基于引用創(chuàng)建裸指針,這種行為是很安全的,但是接下來的方式就不安全了。

let address = 0x012345usize;
let r = address as *const i32;

這里是基于一個內(nèi)存地址來創(chuàng)建裸指針,這種行為相當(dāng)危險,試圖使用任意的內(nèi)存地址往往是一種未定義的行為(undefined behavior),因為該內(nèi)存地址有可能存在值,也有可能沒有,就算有值,也大概率不是你需要的值。

同時編譯器也有可能會優(yōu)化這段代碼,會造成沒有任何內(nèi)存訪問發(fā)送,甚至程序還可能會發(fā)生段錯誤(segmentation fault)。總之,你幾乎沒有好的理由像上面這樣實現(xiàn)代碼,雖然它是可行的。

如果真的要使用內(nèi)存地址,也是類似下面的用法啊,先取地址,再使用,而不是憑空捏造一個地址。

use std::{slice::from_raw_parts, str::from_utf8_unchecked};

// 獲取字符串的內(nèi)存地址和長度
fn get_memory_location() -> (usize, usize) {
  let string = "Hello World!";
  let pointer = string.as_ptr() as usize;
  let length = string.len();
  (pointer, length)
}

// 在指定的內(nèi)存地址讀取字符串
fn get_str_at_location(pointer: usize, length: usize) -> &'static str {
  unsafe { from_utf8_unchecked(from_raw_parts(pointer as *const u8, length)) }
}

fn main() {
  let (pointer, length) = get_memory_location();
  let message = get_str_at_location(pointer, length);
  println!(
    "The {} bytes at 0x{:X} stored: {}",
    length, pointer, message
  );
  // 如果大家想知道為何處理裸指針需要 `unsafe`,可以試著反注釋以下代碼
  // let message = get_str_at_location(1000, 10);
}

1.2.1.4、使用 * 解引用

let a = 1;
let b: *const i32 = &a as *const i32;
let c: *const i32 = &a;
unsafe {
    println!("{}", *c);
}

使用 * 可以對裸指針進(jìn)行解引用,由于該指針的內(nèi)存安全性并沒有任何保證,因此我們需要使用 unsafe 來包裹解引用的邏輯(切記,unsafe 語句塊的范圍一定要盡可能的小,具體原因在上一章節(jié)有講)。

以上代碼另一個值得注意的點就是:除了使用 as 來顯式的轉(zhuǎn)換,我們還使用了隱式的轉(zhuǎn)換方式 let c: *const i32 = &a;。在實際使用中,我們建議使用 as 來轉(zhuǎn)換,因為這種顯式的方式更有助于提醒用戶:你在使用的指針是裸指針,需要小心。

1.2.1.5、基于指針指針創(chuàng)建裸指針

let a: Box<i32> = Box::new(10);
// 需要先解引用a
let b: *const i32 = &*a;
// 使用 into_raw 來創(chuàng)建
let c: *const i32 = Box::into_raw(a);

1.2.1.6、總結(jié)

使用裸指針可以讓我們創(chuàng)建兩個可變指針指向同一個數(shù)據(jù),如果使用安全的 Rust 是無法做到這一點,違背了借用規(guī)則,編譯器會阻止。因此裸指針可以繞過借用規(guī)則,由此帶來的數(shù)據(jù)競爭問題,需要大家自己處理。

重要用途就是跟 C 語言的代碼進(jìn)行交互(FFI),在講解 FFI 之前,先看看調(diào)用 unsafe 函數(shù)和方法。

1.2.2、調(diào)用不安全的函數(shù)或方法

1.2.2.1、調(diào)用不安全的函數(shù)或方法

unsafe 函數(shù)從外表上來看跟普通函數(shù)并無區(qū)別,唯一區(qū)別就是需要使用 unsafe fn 來進(jìn)行定義,這種定義行為告訴調(diào)用者:當(dāng)調(diào)用此函數(shù)時候,你需要注意它的相關(guān)需求,因為 Rust 無法擔(dān)保調(diào)用者在使用該函數(shù)時能滿足它所需要的一切需求。

強制調(diào)用者加上 unsafe 語句塊,就可以讓他清晰認(rèn)識到正在調(diào)用一個不安全的函數(shù),需要小心看看文檔,看看函數(shù)有哪些特別的要求需要被滿足。

unsafe fn dangerous() {}
fn main() {
    dangerous();
}

如果試圖這樣調(diào)用,編譯器就會報錯

error[E0133]: call to unsafe function is unsafe and requires unsafe function or block
 --> src/main.rs:3:5
  |
3 |     dangerous();
  |     ^^^^^^^^^^^ call to unsafe function

范例:修改

unsafe fn dangerous() {}
fn main() {
    unsafe{
    	dangerous();
    }
}

使用 unsafe 聲明的函數(shù)時候,一定要看看相關(guān)文檔,確定自己沒有遺漏什么。

1.2.2.2、用安全抽象包裹 unsafe 代碼

一個函數(shù)包含了 unsafe 不代表我們需要將整個函數(shù)定義 unsafe fn。事實上,在標(biāo)準(zhǔn)庫中有大量的安全函數(shù),他們內(nèi)部都包含了 unsafe 函數(shù)。

對于 Rust 的借用檢查器來說,它無法理解我哦們分別借用了同一個切片的兩個不同部分,但是事實上,這種行為是沒有問題,畢竟兩個借用沒有任何重疊之處。

use std::slice;

fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
    let len = slice.len();
    let ptr = slice.as_mut_ptr();

    assert!(mid <= len);

    unsafe {
        (
            slice::from_raw_parts_mut(ptr, mid),
            slice::from_raw_parts_mut(ptr.add(mid), len - mid),
        )
    }
}

fn main() {
    let mut v = vec![1, 2, 3, 4, 5, 6];

    let r = &mut v[..];

    let (a, b) = split_at_mut(r, 3);

    assert_eq!(a, &mut [1, 2, 3]);
    assert_eq!(b, &mut [4, 5, 6]);
}

相比安全事項,這段代碼沒有那么好理解,我們甚至需要像C語言那樣,通過指針地址的偏移去控制數(shù)組的分割。

1.2.2.3、FFI(Foreign Function Interface)

FFI(Foreign Function Interface) 可以用來與其他語言進(jìn)行交互,但是并不是所有語言都這么稱呼,例如 Java 稱之為 JNI(Java Native Interface)。

FFI 之所以存在是由于現(xiàn)實中很多代碼庫都是由不同語言編寫的,如果我們需要使用某個庫,但是它是由其它語言編寫的,那么往往只有兩個選擇:

  • 對該庫進(jìn)行重寫或移植
  • 使用 FFI

前者相當(dāng)不錯,但是在很多時候,并沒有那么多時間去重寫,因此 FFI 就成了最佳選擇。回到 Rust 語言上,由于這門語言依然很年輕,一些生態(tài)是缺失的,我們在寫一些不是那么大眾的項目時,可能會同時遇到?jīng)]有相應(yīng)的 Rust 庫可用的尷尬境況,此時通過 FFI 去調(diào)用 C 語言的庫就成了相當(dāng)棒的選擇。

還有在將 C/C++ 的代碼重構(gòu)為 Rust 時,先將相關(guān)代碼引入到 Rust 項目中,然后逐步重構(gòu),也是不錯的(為什么用不錯來形容?因為重構(gòu)一個有一定規(guī)模的 C/C++ 項目遠(yuǎn)沒有想象中美好,因此最好的選擇還是對于新項目使用 Rust 實現(xiàn),老項目。。就讓它先運行著吧)。

當(dāng)然,除了 FFI 還有一個辦法可以解決跨語言調(diào)用的問題,那就是將其作為一個獨立的服務(wù),然后使用網(wǎng)絡(luò)調(diào)用的方式去訪問,HTTP,gRPC 都可以。

言歸正傳,之前我們提到 unsafe 的另一個重要目的就是對 FFI 提供支持,它的全稱是 Foreign Function Interface,顧名思義,通過 FFI , 我們的 Rust 代碼可以跟其它語言的外部代碼進(jìn)行交互。

范例:

extern "C" {
    fn abs(input: i32) -> i32;
}

fn main() {
    unsafe {
        println!("Absolute value of -3 according to C: {}", abs(-3));
    }
}

C 語言的代碼定義在了 extern 代碼塊中, 而 extern 必須使用 unsafe 才能進(jìn)行進(jìn)行調(diào)用,原因在于其它語言的代碼并不會強制執(zhí)行 Rust 的規(guī)則,因此 Rust 無法對這些代碼進(jìn)行檢查,最終還是要靠開發(fā)者自己來保證代碼的正確性和程序的安全性。

1.2.2.4、ABI(Application Binary Interface)

ABI 定義了如何在匯編層面來調(diào)用該函數(shù),在所有的ABI中,C語言是最常見的。

1.2.2.5、其他語言調(diào)用 Rust 函數(shù)

在 Rust 中調(diào)用其它語言的函數(shù)是讓 Rust 利用其他語言的生態(tài),那反過來可以嗎?其他語言可以利用 Rust 的生態(tài)不?答案是肯定的。

我們可以使用 extern 來創(chuàng)建一個接口,其它語言可以通過該接口來調(diào)用相關(guān)的 Rust 函數(shù)。但是此處的語法與之前有所不同,之前用的是語句塊,而這里是在函數(shù)定義時加上 extern 關(guān)鍵字,當(dāng)然,別忘了指定相應(yīng)的 ABI:

范例:

#[no_mangle]
pub extern "C" fn call_from_c() {
    println!("Just called a Rust function from C!");
}

上述代碼可以編譯稱一個共享庫,然后鏈接到C語言。

#[no_mangle] 注解告訴編譯器:不要亂改函數(shù)的名稱。

Mangling 的定義是:當(dāng) Rust 因為編譯需要去修改函數(shù)的名稱,例如為了讓名稱包含更多的信息,這樣其它的編譯部分就能從該名稱獲取相應(yīng)的信息,這種修改會導(dǎo)致函數(shù)名變得相當(dāng)不可讀。

因此,為了讓 Rust 函數(shù)能順利被其它語言調(diào)用,我們必須要禁止掉該功能

1.2.3、訪問或修改可變靜態(tài)變量

我們在全局變量章節(jié)中講解過,這里不講述了

1.2.4、實現(xiàn)不安全的Trait

unsafe 的trait 確實不多見,如果大家還記得話,我們在之前的 Send 和 Sync 章節(jié)過實現(xiàn)過 unsafe 特征 Send。

范例:unsafe trait 聲明很簡單

unsafe trait Foo {
    // 方法列表
}

unsafe impl Foo for i32 {
    // 實現(xiàn)相應(yīng)的方法
}

fn main() {}

1.2.5、訪問聯(lián)合體的字段

union 是用于 C 代碼進(jìn)行交互。訪問 union 的字段是不安全的,因為 Rust 無法保證存在在 union 實例中的數(shù)據(jù)類型。

#[repr(C)]
union MyUnion {
    f1: u32,
    f2: f32,
}

從上述代碼可以看出,union 的使用方式跟結(jié)構(gòu)體確實很相似,但是前者的所有字段都共享同一個存儲空間,意味著往 union 的某個字段寫入值,會導(dǎo)致其它字段的值會被覆蓋。

關(guān)于 Union 可以查看 https://doc.rust-lang.org/reference/items/unions.html。

1.2.6、一些實用庫

1、rust-bindgen 和 cbindgen

對于 FFI 的調(diào)用來說,保證接口的正確性是非常重要的,這兩個庫可以幫我們自動生成相應(yīng)的接口,其中 rust-bindgen 用于在 Rust 中訪問 C 代碼,而 cbindgen則反之。

2、cxx

如果需要跟 C++ 代碼交互,非常推薦使用 cxx,它提供了雙向的調(diào)用,最大的優(yōu)點就是安全:是的,你無需通過 unsafe 來使用它!

3、Miri
miri 可以生成 Rust 的中間層表示 MIR,對于編譯器來說,我們的 Rust 代碼首先會被編譯為 MIR ,然后再提交給 LLVM 進(jìn)行處理。

可以通過 rustup component add miri 來安裝它,并通過 cargo miri 來使用,同時還可以使用 cargo miri test 來運行測試代碼。

miri 可以幫助我們檢查常見的未定義行為(UB = Undefined Behavior),以下列出了一部分:

  • 內(nèi)存越界檢查和內(nèi)存釋放后再使用(use-after-free)
  • 使用未初始化的數(shù)據(jù)
  • 數(shù)據(jù)競爭
  • 內(nèi)存對齊問題

但是需要注意的是,它只能幫助識別被執(zhí)行代碼路徑的風(fēng)險,那些未被執(zhí)行到的代碼是沒辦法被識別的。

4、Clippy

官方的 clippy 檢查器提供了有限的 unsafe 支持,雖然不多,但是至少有一定幫助。例如 missing_safety_docs 檢查可以幫助我們檢查哪些 unsafe 函數(shù)遺漏了文檔。

需要注意的是: Rust 編譯器并不會默認(rèn)開啟所有檢查,大家可以調(diào)用 rustc -W help 來看看最新的信息。

5、prusti

prusti 需要大家自己來構(gòu)建一個證明,然后通過它證明代碼中的不變量是正確被使用的,當(dāng)你在安全代碼中使用不安全的不變量時,就會非常有用。

6、模糊測試(fuzz testing)

cargo install cargo-fuzz

二、高級 Trait

2.1、在 Trait定義中使用關(guān)聯(lián)類型來指定占位類型

關(guān)聯(lián)類型(associated type) 是 Trait 中 的類型占位符,它可以用于 Trait 的方法簽名中,可以定義包含某些類型的 Trait ,而實現(xiàn)前無需要知道這些類型是什么

pub trait Iterator {
    type Item;

    fn next(&mut self) -> Option<Self::Item>;
}

2.2、關(guān)聯(lián)類型與泛型區(qū)別

泛型 關(guān)聯(lián)類型
每次實現(xiàn) Trait 時標(biāo)注類型 無需標(biāo)注類型
可以為一個類型多次實現(xiàn)某個 trait(不同的泛型參數(shù)) 無法為單個類型多次實現(xiàn)某個 Trait

2.3、默認(rèn)泛型參數(shù)和運算符重載(operator overloading)

可以在使用泛型參數(shù)時為泛型指定一個默認(rèn)的具體類型,語法 <PlaceholderType=ConcreteType> 這種技術(shù)常用于運算符重載, Rust 不允許創(chuàng)建自己的運算符以及重載任意的運算符,但是可以通過實現(xiàn) std::ops 中列出的那些 Trait 來重載一部分相應(yīng)的運算符。

use std::ops::Add;

#[derive(Debug, Copy, Clone, PartialEq)]
struct Point {
    x: i32,
    y: i32,
}

impl Add for Point {
    type Output = Point;

    fn add(self, other: Point) -> Point {
        Point {
            x: self.x + other.x,
            y: self.y + other.y,
        }
    }
}

fn main() {
    assert_eq!(
        Point { x: 1, y: 0 } + Point { x: 2, y: 3 },
        Point { x: 3, y: 3 }
    );
}

Add Trait 聲明如下

trait Add<Rhs=Self> {
    type Output;

    fn add(self, rhs: Rhs) -> Self::Output;
}

2.4、默認(rèn)泛型參數(shù)的主要場景

  • 擴展一個類型不破壞現(xiàn)有代碼
  • 允許在大部分用戶都不需要的特定場景下進(jìn)行自定義

2.5、完全限定語法(Fully Qualified Syntax)

示例代碼:

trait Pilot {
    fn fly(&self);
}

trait Wizard {
    fn fly(&self);
}

struct Human;

impl Pilot for Human {
    fn fly(&self) {
        println!("This is your captain speaking.");
    }
}

impl Wizard for Human {
    fn fly(&self) {
        println!("Up!");
    }
}

impl Human {
    fn fly(&self) {
        println!("*waving arms furiously*");
    }
}

fn main() {
    let person = Human;
    person.fly();
}

默認(rèn)調(diào)用 Human 的fly方法,我們?nèi)绻{(diào)用 Trait 的實現(xiàn)方法可以使用下列調(diào)用方式

fn main() {
    let person = Human;
    Pilot::fly(&person);
    Wizard::fly(&person);
    person.fly();
}

如果我們要調(diào)用的是關(guān)聯(lián)函數(shù)。

trait Animal {
    fn baby_name() -> String;
}

struct Dog;

impl Dog {
    fn baby_name() -> String {
        String::from("Spot")
    }
}

impl Animal for Dog {
    fn baby_name() -> String {
        String::from("puppy")
    }
}

fn main() {
    println!("A baby dog is called a {}", Dog::baby_name());
}

此時如果我們想要調(diào)用 Animal 的 baby_name 語法,則需要使用 完全限定語法。

fn main() {
    println!("A baby dog is called a {}", <Dog as Animal>::baby_name());
}

完全限定語法形式如下

<Type as Trait>::function(receiver_if_method, next_arg, ...);

2.6、使用 supertrait 要求 trait 附帶其他 trait 的功能

有需要在一個 trait 使用其他 trait。

use std::fmt;

trait OutlinePrint: fmt::Display {
    fn outline_print(&self) {
        let output = self.to_string();
        let len = output.len();
        println!("{}", "*".repeat(len + 4));
        println!("*{}*", " ".repeat(len + 2));
        println!("* {} *", output);
        println!("*{}*", " ".repeat(len + 2));
        println!("{}", "*".repeat(len + 4));
    }
}

2.7、使用 newtype 模式在外部類型上實現(xiàn)外部 trait

孤兒規(guī)則:只有當(dāng) trait 或 類型定義在本地包,才能為該類型實現(xiàn)這個trait。

可以通過 newtype 模式來繞過這一規(guī)則,利用 tuple struct 創(chuàng)建一個新的類型。

use std::fmt;

struct Wrapper(Vec<String>);

impl fmt::Display for Wrapper {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "[{}]", self.0.join(", "))
    }
}

fn main() {
    let w = Wrapper(vec![String::from("hello"), String::from("world")]);
    println!("w = {}", w);
}

三、高級類型

3.1、newtype模式

  • 用來靜態(tài)的保證各種值之間不會混淆并表明值的單位;
  • 為類型的某些細(xì)節(jié)提供抽象能力
  • 通過輕量級的封裝來隱藏內(nèi)部實現(xiàn)細(xì)節(jié)

3.2、使用類型別名創(chuàng)建類型同義詞

使用type 關(guān)鍵字創(chuàng)建類型別名,類型別名并不是獨立的類型。主要用途是減少代碼字符重復(fù)

    type Kilometers = i32;

    let x: i32 = 5;
    let y: Kilometers = 5;

    println!("x + y = {}", x + y);

3.3、Never type

有一個名為 ! 的特殊類型,它沒有任何值,行話叫做空類型(empty type),它在不返回的函數(shù)中充當(dāng)返回類型

不返回值的函數(shù)也被稱做發(fā)散函數(shù)(diverging function)

fn bar() -> ! {
    // --snip--
}

這種代碼會報錯。

loop循環(huán) 和 continue 的返回類型就是 !

3.3、動態(tài)大小 和 Size Trait

3.3.1、動態(tài)大小

Rust 需要在編譯時確定為一個特定的類型值分配多少空間。動態(tài)大小的類型(Dynamically Sized Types, DST)概念,編寫代碼時使用只有在運行時才能確定大小的值。

Rust 使用動態(tài)大小類型的通用方式:附帶一些元數(shù)據(jù)來存儲動態(tài)信息的大小,使用動態(tài)類型大小總會把它的值放在某種指針后面。

3.3.2、Sized Trait

為了處理動態(tài)大小的類型, Rust 提供了一個 Sized Trait 來確定一個類型的大小在編譯時是否已知

  • 編譯時可以計算出大小的類型會自動實現(xiàn)這一個 trait
  • Rust 還會為每個泛型函數(shù)隱式添加 Sized 約束

3.3.4、?Sized Trait 約束

使用 ?Sized 表示 泛型 可能是 Sized 也可能不是Sized,參數(shù)必須是引用。

四、高級函數(shù)和閉包

4.1、函數(shù)指針(function pointer)

可以將函數(shù)傳遞給其他函數(shù),函數(shù)在傳遞過程中會被強制轉(zhuǎn)換 fn 類型,fn 類型就是函數(shù)指針。

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);
}

函數(shù)指針是一個類型,不是一個 trait,全部實現(xiàn)了三種閉包 trait。

4.2、返回閉包

fn returns_closure() -> Box<dyn Fn(i32) -> i32> {
    Box::new(|x| x + 1)
}

五、宏(macro)

5.1、宏

宏在 Rust 里面指的是一組相關(guān)特性的集合稱謂

  • 使用 macro_rules! 構(gòu)建聲明宏(declarative macro)
  • 三種過程宏
    • 自定義 #[derive] 宏,用于 struct 或 enum,可以為其制定隨 derive 屬性添加的代碼
    • 類似屬性的宏(Attribute-like macro),在任意條目上添加自定義屬性
    • 類似函數(shù)的宏(Function-like macro),看起來像函數(shù)調(diào)用,對其指定為參數(shù)的 token 進(jìn)行操作

5.2、宏和函數(shù)的區(qū)別

  • 宏是通過一種代碼生成另一種代碼,Rust 的函數(shù)簽名是固定的,而宏可以擁有可變數(shù)量的參數(shù)。
  • 編譯器會在解釋代碼進(jìn)行展開宏
  • 函數(shù)可以在任何位置定義和使用

5.3、macro_rules! 聲明宏

#[macro_export]
macro_rules! vec {
    ( $( $x:expr ),* ) => {
        {
            let mut temp_vec = Vec::new();
            $(
                temp_vec.push($x);
            )*
            temp_vec
        }
    };
}

#[macro_export] 注釋將宏進(jìn)行了導(dǎo)出,這樣其他包可以將宏導(dǎo)入到當(dāng)前座女與中,然后才能使用。標(biāo)準(zhǔn)庫 vec!宏已經(jīng)通過 std::prelude 自動引入。

對于 macro_rules! 存在一些問題,Rust 計劃在未來使用新的生命宏來替換它。

5.4、用過程宏(procedural macros)為屬性標(biāo)記生成代碼

從形式上來看,過程宏跟函數(shù)較為相像,但過程宏是使用源代碼作為輸入?yún)?shù),基于代碼進(jìn)行一系列操作后,再輸出一段全新的代碼。注意,過程宏中的 derive 宏輸出的代碼并不會替換之前的代碼,這一點與聲明宏有很大的不同!

有三種類型:自定義 derive、屬性宏、函數(shù)宏。

5.4.1、自定義 derive 宏

extern crate proc_macro;

use proc_macro::TokenStream;
use quote::quote;
use syn;
use syn::DeriveInput;

#[proc_macro_derive(HelloMacro)]
pub fn hello_macro_derive(input: TokenStream) -> TokenStream {
    // 基于 input 構(gòu)建 AST 語法樹
    let ast:DeriveInput = syn::parse(input).unwrap();

    // 構(gòu)建特征實現(xiàn)代碼
    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()
}

5.4.2、屬性宏

#[proc_macro_attribute]
pub fn route(attr: TokenStream, item: TokenStream) -> TokenStream {

5.4.3、函數(shù)宏

#[proc_macro]
pub fn sql(input: TokenStream) -> TokenStream {

總結(jié)

以上就是今天要講的內(nèi)容文章來源地址http://www.zghlxwxcb.cn/news/detail-682559.html

到了這里,關(guān)于【跟小嘉學(xué) Rust 編程】十九、高級特性的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實不符,請點擊違法舉報進(jìn)行投訴反饋,一經(jīng)查實,立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費用

相關(guān)文章

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包