我們?cè)诰幊讨?,通常有這樣的需求,為多種類型的數(shù)據(jù)編寫一個(gè)功能相同的函數(shù),如兩個(gè)數(shù)的加法,希望這個(gè)函數(shù)既支持i8、i16、 i32 ....float64等等,甚至自定義類型,在不支持泛型的編程語言中,我們通常要為每一種類型都編寫一個(gè)函數(shù),而且通常情況下函數(shù)名還必須不同,例如:
fn add_i8(a:i8, b:i8) -> i8 {
a + b
}
fn add_i16(a:i16, b:i16) -> i16 {
a + b
}
fn add_f64(a:f64, b:f64) -> f64 {
a + b
}
// 各種其他add函數(shù)
// ...
fn main() {
println!("add i8: {}", add_i8(2i8, 3i8));
println!("add i16: {}", add_i16(20i16, 30i16));
println!("add f64: {}", add_f64(1.23, 1.23));
}
如果有很多地方都需要支持多種類型,那么代碼量就會(huì)非常大,而且代碼也會(huì)非常臃腫,編程就真的變成了苦逼搬磚的工作,枯燥而乏味:D。 學(xué)過C++的人也許很容易理解泛型,但本教程面向的是Rust初學(xué)者,所以不會(huì)拿C++的泛型、多態(tài)和Rust進(jìn)行對(duì)比,以免增加學(xué)習(xí)的復(fù)雜度和不必要的困擾,從而讓Rust初學(xué)者更容易理解和接受Rust泛型。
泛型程序設(shè)計(jì)是程序設(shè)計(jì)語言的一種風(fēng)格或范式。允許程序員在強(qiáng)類型程序設(shè)計(jì)語言中編寫代碼時(shí)使用一些以后才指定的類型,在實(shí)例化時(shí)(instantiate)作為參數(shù)指明這些類型(在Rust中,有的時(shí)候類型還可以被編譯器推導(dǎo)出來)。各種程序設(shè)計(jì)語言和其編譯器、運(yùn)行環(huán)境對(duì)泛型的支持均不一樣。Ada, Delphi, Eiffel, Java, C#, F#, Swift, and Visual Basic .NET稱之為泛型(generics);ML, Scala and Haskell稱之為參數(shù)多態(tài)(parametric polymorphism);C++與D稱之為模板。具有廣泛影響的1994年版的《Design Patterns》一書稱之為參數(shù)化類型(parameterized type)。
提示: 以上概念摘自《維基百科-泛型》
在編程的時(shí)候,我們經(jīng)常利用多態(tài)。通俗的講,多態(tài)就是好比坦克的炮管,既可以發(fā)射普通彈藥,也可以發(fā)射制導(dǎo)炮彈(導(dǎo)彈),也可以發(fā)射貧鈾穿甲彈,甚至發(fā)射子母彈,大家都不想為每一種炮彈都在坦克上分別安裝一個(gè)專用炮管,即使生產(chǎn)商愿意,炮手也不愿意,累死人啊。所以在編程開發(fā)中,我們也需要這樣“通用的炮管”,這個(gè)“通用的炮管”就是多態(tài)。
需要知道的是,泛型就是一種多態(tài)。
泛型主要目的是為程序員提供了編程的便利,減少代碼的臃腫,同時(shí)極大豐富了語言本身的表達(dá)能力, 為程序員提供了一個(gè)合適的炮管。想想,一個(gè)函數(shù),代替了幾十個(gè),甚至數(shù)百個(gè)函數(shù),是一件多么讓人興奮的事情。 泛型,可以理解為具有某些功能共性的集合類型,如i8、i16、u8、f32等都可以支持add,甚至兩個(gè)struct Point類型也可以add形成一個(gè)新的Point。
先讓我們來看看標(biāo)準(zhǔn)庫中常見的泛型Option
enum Option<T> {
Some(T),
None,
}
T就是泛型參數(shù),這里的T可以換成A-Z任何你自己喜歡的字母。不過習(xí)慣上,我們用T表示Type,用E表示Error。T在具體使用的時(shí)候才會(huì)被實(shí)例化:
let a = Some(100.111f32);
編譯器會(huì)自行推導(dǎo)出a為Option
當(dāng)然,你也可以顯式聲明a的類型,但必須保證和右值的類型一樣,不然編譯器會(huì)報(bào)"mismatched types"類型不匹配錯(cuò)誤。
let a:Option<f32> = Some(100.111); //編譯自動(dòng)推導(dǎo)右值中的100.111為f32類型。
let b:Option<f32> = Some(100.111f32);
let c:Option<f64> = Some(100.111);
let d:Option<f64> = Some(100.111f64);
至此,我們已經(jīng)了解到泛型的定義和簡單的使用了。 現(xiàn)在讓我們用函數(shù)重寫add操作:
use std::ops::Add;
fn add<T: Add<T, Output=T>>(a:T, b:T) -> T {
a + b
}
fn main() {
println!("{}", add(100i32, 1i32));
println!("{}", add(100.11f32, 100.22f32));
}
輸出: 101 200.33
add<T: Add<T, Output=T>>(a:T, b:T) -> T
就是我們泛型函數(shù),返回值也是泛型T,Add<>中的含義可以暫時(shí)忽略,大體意思就是只要參數(shù)類型實(shí)現(xiàn)了Add trait,就可以被傳入到我們的add函數(shù),因?yàn)槲覀兊腶dd函數(shù)中有相加+操作,所以要求傳進(jìn)來的參數(shù)類型必須是可相加的,也就是必須實(shí)現(xiàn)了Add trait(具體參考std::ops::Add)。
上面的例子,add的都是語言內(nèi)置的基礎(chǔ)數(shù)據(jù)類型,當(dāng)然我們也可以為自己自定義的數(shù)據(jù)結(jié)構(gòu)類型實(shí)現(xiàn)add操作。
use std::ops::Add;
#[derive(Debug)]
struct Point {
x: i32,
y: i32,
}
// 為Point實(shí)現(xiàn)Add trait
impl Add for Point {
type Output = Point; //執(zhí)行返回值類型為Point
fn add(self, p: Point) -> Point {
Point{
x: self.x + p.x,
y: self.y + p.y,
}
}
}
fn add<T: Add<T, Output=T>>(a:T, b:T) -> T {
a + b
}
fn main() {
println!("{}", add(100i32, 1i32));
println!("{}", add(100.11f32, 100.22f32));
let p1 = Point{x: 1, y: 1};
let p2 = Point{x: 2, y: 2};
println!("{:?}", add(p1, p2));
}
輸出: 101 200.33 Point { x: 3, y: 3 }
上面的例子稍微更復(fù)雜些了,只是我們?cè)黾恿俗远x的類型,然后讓add函數(shù)依然可以在上面工作。如果對(duì)trait不熟悉,請(qǐng)查閱trait相關(guān)章節(jié)。
大家可能會(huì)疑問,那我們是否可以讓Point也變成泛型的,這樣Point的x和y也能夠支持float類型或者其他類型,答案當(dāng)然是可以的。
use std::ops::Add;
#[derive(Debug)]
struct Point<T: Add<T, Output = T>> { //限制類型T必須實(shí)現(xiàn)了Add trait,否則無法進(jìn)行+操作。
x: T,
y: T,
}
impl<T: Add<T, Output = T>> Add for Point<T> {
type Output = Point<T>;
fn add(self, p: Point<T>) -> Point<T> {
Point{
x: self.x + p.x,
y: self.y + p.y,
}
}
}
fn add<T: Add<T, Output=T>>(a:T, b:T) -> T {
a + b
}
fn main() {
let p1 = Point{x: 1.1f32, y: 1.1f32};
let p2 = Point{x: 2.1f32, y: 2.1f32};
println!("{:?}", add(p1, p2));
let p3 = Point{x: 1i32, y: 1i32};
let p4 = Point{x: 2i32, y: 2i32};
println!("{:?}", add(p3, p4));
}
輸出: Point { x: 3.2, y: 3.2 } Point { x: 3, y: 3 }
上面的列子更復(fù)雜了些,我們不僅讓自定義的Point類型支持了add操作,同時(shí)我們也為Point做了泛型化。
當(dāng)let p1 = Point{x: 1.1f32, y: 1.1f32};
時(shí),Point的T推導(dǎo)為f32類型,這樣Point的x和y屬性均成了f32類型。因?yàn)閜1.x+p2.x,所以T類型必須支持Add trait。
上面區(qū)區(qū)幾十行的代碼,卻實(shí)現(xiàn)了非泛型語言百行甚至千行代碼才能達(dá)到的效果,足見泛型的強(qiáng)大。
有時(shí)候我們可能做些文本分析工作, 數(shù)據(jù)可能來源于外部或者程序內(nèi)置的文本.
請(qǐng)實(shí)現(xiàn)一個(gè) parse
函數(shù), 只接收一個(gè) lines iterator 為參數(shù), 并輸出每行.
要求既能輸出內(nèi)置的文本, 也能輸出文件內(nèi)容.
let lines = "some\nlong\ntext".lines()
parse(do_something_or_nothing(lines))
some
long
text
use std::fs:File;
use std::io::prelude::*;
use std::io::BufReader;
let lines = BufReader::new(File::open("/etc/hosts").unwrap()).lines()
parse(do_some_other_thing_or_nothing(lines))
127.0.0.1 localhost.localdomain localhost
::1 localhost.localdomain localhost
...
本書類型系統(tǒng)中的幾個(gè)常見 trait
章節(jié)中介紹的 AsRef, Borrow 等 trait 應(yīng)該能派上用場.