Rust 中的泛型和 Const 泛型
通常,我們編寫的函數參數一般接受具體類型來或任何用戶定義的類型,即當我們傳遞類型給參數時,類型是已知的。
fn sum_i32(i: &[i32]) -> i32 {
i.iter().sum()
}
fn sum_f32(i: &[f32]) -> f32 {
i.iter().sum()
}
fn sum_i8(i: &[i8]) -> i8 {
i.iter().sum()
}
fn main() {
println!("{}", sum_i8(&[1i8, 4, 6, 7]));
println!("{}", sum_i32(&[1, 2, 3, 4]));
println!("{}", sum_f32(&[1.0f32, 2.0, 3.0, 4.0]));
}
在上面的代碼中,用於對給定切片的元素求和。這裏的問題是,Rust 中總共有十個整數類型,還有兩個浮點類型。
如果我們要爲每種類型寫求和函數,那麼我們總共有 12 個實現來完成求和任務。對於其他任務,比如在切片中查找元素,我們關心的每種類型都需要相同數量的實現。對於一個簡單的任務,我們不僅有更多的代碼,而且測試它們也很重要,因爲我們必須爲相同類型和其他類型編寫不同的測試,這增加了代碼的大小,從而增加了 bug 的可能性。
這對於任何涉及不同類型的相同算法的任務都是相同的。爲了解決這個問題,編程語言必須提供一種通過泛型來定義抽象算法的方法,而不僅僅是像上面的代碼那樣爲每個類型去寫具體的函數。
泛型術語
每種語言都提供了一些定義泛型的方法,這裏我們將看到 Rust 如何定義泛型以及泛型的實現。在討論 rust 泛型之前,我們必須討論一些與泛型相關的術語。
-
泛型或抽象類型:當我們調用它們時,它們就像實際類型的佔位符。
-
具體類型:我們調用的是實際類型。例如,當我們使用一個沒有抽象的 u32 切片調用 sum_u32 函數時。
-
靜態分發:注意,不是所有的語言都像 python、perl、ruby 那樣在編譯時知道類型。在編譯時知道類型可以使編譯器在沒有任何運行時開銷的情況下生成優化的代碼。這被稱爲單態化,即編譯器在編譯時爲被調用的類型生成每個特定的實現。這並不特定於泛型,實際上,如果類型在編譯時已知,編譯器會生成優化的代碼,這就是爲什麼靜態類型語言比動態類型語言更有效,因爲動態類型語言只在運行時才知道類型,而且在編譯時就能捕獲錯誤。靜態分發的缺點是它們增加了編譯時間和二進制文件的大小,因爲編譯器在後臺生成了更多的代碼,儘管它們在源代碼中看起來很簡單。
-
動態分發或虛擬調度:顧名思義,類型僅在運行時已知。這減少了二進制大小和編譯時間,因爲我們不需要生成任何代碼,直到運行時。爲了在運行時正確調用這些方法,該語言有一個指向它們的指針。在 Rust 中,這是通過 fat 指針完成的,它是兩個字的大小,即 16 字節或 128 位。無論它們有多少方法,在編譯時大小總是 16 字節。
泛型是一種重用代碼或算法的方法,它們共享一些共同的操作和屬性,並且避免大量的樣板代碼,減少了源代碼的大小,從而減少了代碼維護和 bug。
Rust 泛型的優勢
Rust 編程語言中泛型的優勢如下:
-
零成本抽象:提供某種形式的抽象,因爲我們不需要知道實現的細節,只需要知道如何使用它們。Rust 抽象是零成本的,也就是說,你不使用的東西,你不需要支付任何開銷。
-
實現多態性:因爲它可以接受不同的類型,而不是單一的具體類型。
-
類型安全:trait 邊界保護我們不會在編譯時通過靜態分派或運行時通過動態分派將無效類型傳遞給 API。
-
類型推斷:在大多數情況下,Rust 能夠在沒有顯式類型的情況下推斷類型。
-
代碼更有條理:幫助更好的 API 設計。
泛型:靜態分發
下面的代碼是靜態分發的:
use std::iter::Sum;
fn main() {
println!("i8 Sum: {} \nu16 Sum: {} \nusize Sum: {} \nf64 Sum: {} \nf32 Sum: {} "
,generic_sum(&[1i8, 4, 6, 7])
,generic_sum(&[1u16, 5, 9, 56])
,generic_sum(&[9usize, 34, 53, 57])
,generic_sum(&[1.9, 4.6, 6.7, 7.9])
,generic_sum(&[1.0f32, 2.0, 3.0, 4.0]));
}
fn generic_sum<T: Sum<T> + Copy>(i: &[T]) -> T {
i.iter().copied().sum()
}
類型 T 是泛型,在冒號後面,它們被稱爲 trait 邊界,不是任何具體類型,而是同時實現 Sum 和 Copy trait 的類型。這就是爲什麼像下面這樣的 rust 代碼一開始就無法編譯的原因,因爲不是所有類型都可以 add。與 C++ 模板不同,rust 在編譯時提供了安全性,錯誤消息爲問題提供了更多的上下文。
fn add<T>(x: T,y: T) -> T {
return x + y;
}
上面的代碼需要強制使用約束類型,從而避免運行時錯誤。我們可以在函數簽名中定義任意多的類型,但最好只使用幾種類型。
use std::ops::{Add, Mul};
fn add_mul<A, B>(x: A, y: A, w: B, z: B) -> B
where
A: Add<A> + Copy,
B: Mul<B, Output = B> + Copy,
{
let var = x + y;
w * z
}
Const 泛型
Const 泛型是對值而不是類型的泛型。在定義任務時,當我們希望值是通用的而不是特定的值時,這很有用。const 泛型僅支持整數、Bool、Char 類型。
use std::fmt::{Debug, Display};
fn main() {
const_generics1::<true, &str>("string");
const_generics1::<false, i32>(67);
// 在這段代碼中,rust能夠推斷類型和值。
const_generics2([1, 2, 3, 4]);
const_generics2(['a', 'b', 'c']);
let m: [char; 3] = ['a', 'b', 'a'];
const_generics2(m);
}
fn const_generics1<const A: bool, T: Display>(i: T) {
if A {
println!("This is True");
} else {
println!("This is false");
}
println!("{}", i);
}
fn const_generics2<T, const N: usize>(i: [T; N])
where
T: Debug,
{
println!("{:?}", i);
}
Const 泛型是值而不是類型,這意味着我們不能在需要類型的地方使用它們。在數組類型 [T;N] 中,T 爲類型,N 爲值。
// 使用const值作爲類型會導致編譯錯誤。
fn const_generics3<const T:char,const N:usize>(i:[T;N]){}
這裏不能在 T 位置使用 const 泛型,但可以在 N 的位置使用 const 泛型,我們可以將不變量編碼爲我們的類型,而不必在運行時捕獲它們,即在編譯時捕獲錯誤。
泛型:動態分發
對於某些類型,我們無法在編譯時知道它們的大小。因此,它們被用在指針後面進行動態分發。
Trait Object 是沒有大小的,因此它們必須在指針後面使用,以便在編譯時具有大小,因爲 Rust 需要在編譯時知道所有類型的大小。
use std::mem::size_of;
fn main() {
// let unsize1: ToString;
// let unsize2: Fn(&str) -> bool;
let sized1: &dyn ToString;
sized1 = &"45";
let sized2: Box<String>;
sized2 = Box::new(String::from(
"Bytes are counted for memory-constrained devices",
));
println!("{:?}", size_of::<&dyn ToString>());
println!("{:?}", size_of::<Box<String>>());
println!("{:?}", size_of::<Box<dyn Fn(&str) -> bool>>());
}
如果我們去掉前兩行的註釋,我們會得到一個編譯錯誤,說 “在編譯時沒有已知的大小”。
size1 的大小爲 16 字節,size2 的大小爲 8 字節。對於 size2 來說,這是一個優勢,因爲普通 String 類型佔用 24 字節,而 Box 佔用 8 字節。
注意 trait object 的 Box 佔用 16 個字節,而普通類型的 Box 佔用 8 個字節。這是一種權衡。如果不需要運行時開銷,那麼選擇靜態分發。如果更關心二進制大小而不是效率,請選擇動態調度。這些都是可選擇的,而不是默認選項。
雖然類型在運行時纔是已知的,但是並不意味着缺乏類型安全性。我們不能在編譯時調用與特徵無關的方法。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/bVwV2a6ibef_aCJOEpkZUQ