鍍金池/ 教程/ Java/ 泛型
標(biāo)準(zhǔn)輸入與輸出
消息傳遞
循環(huán)
注釋
Rust for Mac OS
幾種智能指針
Cell, RefCell
trait對(duì)象 (trait object)
rust web 開發(fā)
Unsafe、原始指針
Macro
迭代器
函數(shù)
Borrow, BorrowMut, ToOwned
快速上手
二叉樹
編輯器
測試與評(píng)測
Deref
安裝Rust
哈希表 HashMap
原生類型
17.錯(cuò)誤處理
VS Code 安裝配置
動(dòng)態(tài)數(shù)組Vec
模式匹配
操作符和格式化字符串
Rust for Linux
函數(shù)參數(shù)
Visual Studio
vim/GVim安裝配置
閉包作為參數(shù)和返回值
安全(Safety)
Cow
生命周期( Lifetime )
閉包的實(shí)現(xiàn)
所有權(quán)(Ownership)
Atom
將Rust編譯成庫
類型、運(yùn)算符和字符串
類型系統(tǒng)中的幾個(gè)常見 trait
特性
屬性和編譯器參數(shù)
Spacemacs
集合類型
Rust json處理
Heap & Stack
并行
標(biāo)準(zhǔn)庫示例
基本程序結(jié)構(gòu)
鏈表
trait 和 trait對(duì)象
前期準(zhǔn)備
代碼風(fēng)格
編譯器參數(shù)
基于語義化版本的項(xiàng)目版本聲明與管理
Rust 版本管理工具: rustup
引用&借用(References&Borrowing)
注釋與文檔
10.1 trait關(guān)鍵字
模式
調(diào)用ffi函數(shù)
unsafe
并發(fā),并行,多線程編程
AsRef 和 AsMut
Rust旅程
Rust for Windows
結(jié)構(gòu)體與枚舉
條件分支
附錄I-術(shù)語表
變量綁定與原生類型
Mutex 與 RwLock
泛型
裸指針
常用數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)
系統(tǒng)命令:調(diào)用grep
Into/From 及其在 String 和 &str 互轉(zhuǎn)上的應(yīng)用
共享內(nèi)存
Sublime
網(wǎng)絡(luò)模塊:W貓的回音
函數(shù)返回值
包和模塊
高階函數(shù)
函數(shù)與方法
match關(guān)鍵字
隊(duì)列
目錄操作:簡單grep
語句和表達(dá)式
并發(fā)編程
閉包
測試
閉包的語法
同步
迭代器
String
Send 和 Sync
Rc 和 Arc
屬性
Emacs
優(yōu)先隊(duì)列
Prelude
cargo簡介
控制流(control flow)
數(shù)組、動(dòng)態(tài)數(shù)組和字符串
FFI
模塊和包系統(tǒng)、Prelude
實(shí)戰(zhàn)篇
Rust 是一門系統(tǒng)級(jí)編程語言,被設(shè)計(jì)為保證內(nèi)存和線程安全,并防止段錯(cuò)誤。作為系統(tǒng)級(jí)編程語言,它的基本理念是 “零開銷抽象”。理
運(yùn)算符重載
Any和反射
rust數(shù)據(jù)庫操作
輸入輸出流
復(fù)合類型
性能測試

泛型

我們?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類型,也就是說Option中的T在這里是f32類型。

當(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);

泛型函數(shù)

至此,我們已經(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。

總結(jié)

上面區(qū)區(qū)幾十行的代碼,卻實(shí)現(xiàn)了非泛型語言百行甚至千行代碼才能達(dá)到的效果,足見泛型的強(qiáng)大。

習(xí)題

1. Generic lines iterator

問題描述

有時(shí)候我們可能做些文本分析工作, 數(shù)據(jù)可能來源于外部或者程序內(nèi)置的文本.

請(qǐng)實(shí)現(xiàn)一個(gè) parse 函數(shù), 只接收一個(gè) lines iterator 為參數(shù), 并輸出每行.

要求既能輸出內(nèi)置的文本, 也能輸出文件內(nèi)容.

調(diào)用方式及輸出參考
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
...
Hint

本書類型系統(tǒng)中的幾個(gè)常見 trait章節(jié)中介紹的 AsRef, Borrow 等 trait 應(yīng)該能派上用場.

上一篇:Atom下一篇:所有權(quán)(Ownership)