概述
相對其他語言(java/C#)提供了接口來滿足對不同類型的值進行操作的代碼(甚至包括那些尚未實現(xiàn)的類型)柑船,并能夠結(jié)合泛型來實現(xiàn)語言的“多態(tài)化”霞溪;同樣Rust為了達到對“多態(tài)”的支持提供了trait(特型)和generic(泛型)。
而trait(特型)算是rust對接口或抽象基類的實現(xiàn)链嘀。
舉個類型: 標準庫中關(guān)于Write定義 std::io::Write
trait Write {
fn write(&mut self, buf: &[u8]) -> Result<usize>;
fn flush(&mut self) -> Result<()>;
fn write_all(&mut self, buf: &[u8]) -> Result<()> {
// 省略部分代碼
}
// 省略代碼
}
同樣在標準類型庫中File兜叨、TcpStream和Vec<u8>等實現(xiàn)了std::io::Write
use std::io::Write; // 需要引入對應(yīng)的trait
fn say_hello(out: &mut Write) -> std::io::Result<()> {
out,write_all(b"welcome to rust world!!!")?; // 使用Vec<u8>
out.flush();
}
/// 測試用例
use std::fs::File;
fn main() {
let mut file = std::fs::File::create("hello_world.txt").unwrap();
say_hello(&mut file);
let mut bytes = vec![];
say_hello(&mut bytes);
println!("bytes={:?}", bytes);
}
同樣使用trait(特型)添加給類型不用額外內(nèi)存
使用trait(特型)
在rust中trait(特型)是一種任何類型都可以支持或不支持;trait可以認為某類型能夠做什么的一種能力谱姓;(當使用trait特型方法時需要確保其本身必須存在當前作用域中借尿,否則trait特型所有的方法都是不可用的;若是trait特型是標準前置模塊的一部分屉来,這樣rust就會默認自動將其引入)路翻。
1、trait object(特型對象/特型目標)
use std::io::Write;
fn main() {
let mut buf: Vec<u8> = vec![];
// let writer: Write = buf; // 不能通過編譯茄靠,編譯時不知道write真實大小
let writer: &mut Write = &mut buf;
}
上面的例子中l(wèi)ine-error-1這行是不能通過編譯的帚桩,在rust中變量大小在編譯期間必須可知的,但是實現(xiàn)Write的類型大小是任意的嘹黔,這一點不同于其他語言(java/C#)账嚎,因為這些語言默認定義的變量是一個引用,并執(zhí)行任何實現(xiàn)了interface或abstract的對象儡蔓;但是在rust的引用必須是顯式的郭蕉。并且在rust將一個trait特型類型的引用稱為trait object(特型對象/特型目標)。
不過需要知道的是:trait object(特型對象/特型目標)在編譯期間通常是不知道引用對象/目標的類型喂江,那就需要trait object(特型對象/特型目標)包含一些關(guān)于trait object(特型對象/特型目標)的額外信息召锈,并且這個信息僅限r(nóng)ust內(nèi)部自身使用,確保在調(diào)用trait特型方法時知道類型信息获询,這樣才能動態(tài)調(diào)用正確的方法涨岁。
2、trait object(特型對象/特型目標)的內(nèi)存分布
在內(nèi)存中, trait object(特型對象/特型目標)是一個胖指針,包含指向值的指針和指向表示該值類型的表的指針吉嚣; 每個trait object(特型對象/特型目標)都會占用兩個機器字梢薪;
let mut buf: Vec<u8> = vec![];
let writer: &mut Write = &mut buf;
內(nèi)存布局如下:
data:數(shù)據(jù)指針內(nèi)容 | &mut buf as &mut Write | 值類型的表指針 | ||||
---|---|---|---|---|---|---|
buf | data:數(shù)據(jù)指針 | destructor | ||||
buffer:數(shù)據(jù) | vptr :值類型的表指針 | size | ||||
capacity: 容量 | alignment | |||||
length: 大小 | .wirte() | |||||
.flush() | ||||||
.write_all() |
在rust中會在編譯時生成虛擬表并且只生成一次,并有同類型的所有對象共享(impl trait_name for traint_imple_name)尝哆;這樣在調(diào)用一個trait object(特型對象/特型目標)的方法時秉撇,會自動使用虛擬表,已確定調(diào)用了哪個實現(xiàn)秋泄。
3琐馆、traint特型的定義和實現(xiàn)
在前面的例子中定義trait特型其實是很簡單的:
關(guān)鍵字trait Trait_Name {
// trait體 定義不同的方法
// 方法
}
樣例:
trait Action {
fn draw(&self, canvas: &mut Canvas);
fn hit_test(&self, x:i32, y:i32) -> bool;
}
同樣,實現(xiàn)trait也是相對比較簡單的:
關(guān)鍵字 impl Trait_Name for Type_Name {
// 實現(xiàn)的方法
}
樣例:
impl Action for Broom {
fn draw(&self, canvas: &mut Canvas) {
for y in self.y - self.height -1 .. self.y {
canvas.write_at(self.x, y, '|');
}
canvas.write_at(self.x, self.y, 'M');
}
fn hit_test(&self, x: i32, y: i32) -> bool {
self.x == x
&& self.y - self.height - 1 <= y
&& y <= self.y
}
}
而非trait特型的方法恒序,可以使用impl Type_Name來定義:
impl Type_Name {
fn method_name(&self, ...<參數(shù)>...);
fn method_name(...<參數(shù)>...);
...
}
不過在實現(xiàn)trait時瘦麸,若是其中某個或某些方法相對通用,可以在trait特型中提供當前方法的默認實現(xiàn)歧胁。 這樣通過impl Trait_Name for Type_Name 語法來實現(xiàn)trait時滋饲,可以使用這個默認方法彤敛。
在Rust允許在任意類型上實現(xiàn)任意trait特型,這樣就可以給任意類型擴張功能:
樣例: 擴展現(xiàn)有char類型的功能
trait IsEmoji {
fn is_emlji(&self) -> bool;
}
impl IsEmoji for char {
fn is_emoji(&self) -> bool {
...
}
}
同樣了赌,可以一次性全部實現(xiàn)一個擴展trait特型: 使用泛型impl語法讓一個類型“家族”都具有某個擴展trait特型(關(guān)于泛型這塊會有單獨的介紹)墨榄。
樣例:對每個實現(xiàn)Write的類型,在實現(xiàn)trait特型WriteHtml
use std::io::{self, Write};
trait WriteHtml {
fn write_html(&mut self, html: &HtmlDocument) -> io::Result;
}
impl<W: Write> WriteHtml for W {
fn write_html(&mut self, html: &HtmlDocument) -> io::Result<()> {
......
}
}
在rust中trait特型也能夠?qū)崿F(xiàn)其他語言的“繼承”勿她, 通過impl來實現(xiàn)trait時需要實現(xiàn)當前trait特型及其父trait特型袄秩。
樣例:子trait特型
trait Trait_Name: Parent_Trait_Name {
...
}