你還記得 impl 關(guān)鍵字嗎,它用于調(diào)用一個(gè)函數(shù)的 methodsyntax
struct Circle {
x: f64,
y: f64,
radius: f64,
}
impl Circle {
fn area(&self) -> f64 {
std::f64::consts::PI * (self.radius * self.radius)
}
}
特征幾乎都是相似的,除了我們用方法簽名去定義一個(gè)特征,然后實(shí)現(xiàn)該結(jié)構(gòu)的特征。如下面所示:
struct Circle {
x: f64,
y: f64,
radius: f64,
}
trait HasArea {
fn area(&self) -> f64;
}
impl HasArea for Circle {
fn area(&self) -> f64 {
std::f64::consts::PI * (self.radius * self.radius)
}
}
如您所見(jiàn),這個(gè)特征塊和 impl 塊非常相似,但我們不定義一個(gè)主體,只是定義一個(gè)類(lèi)型簽名。當(dāng)我們 impl 一個(gè)特征時(shí),我們使用 impl 特征項(xiàng),而不僅僅是 impl 項(xiàng)。
我們可以使用特征約束泛型。思考下面這個(gè)函數(shù),它沒(méi)有編譯,只給我們一個(gè)類(lèi)似的錯(cuò)誤:
fn print_area<T>(shape: T) {
println!("This shape has an area of {}", shape.area());
}
Rust 可能會(huì)抱怨道:
error: type `T` does not implement any method in scope named `area`
因?yàn)?T 可以是任何類(lèi)型,我們不能確保它實(shí)現(xiàn)了 area 的方法。但我們可以添加一個(gè)特征約束的泛型 T,確保它已經(jīng)實(shí)現(xiàn):
fn print_area<T: HasArea>(shape: T) {
println!("This shape has an area of {}", shape.area());
}
語(yǔ)法 < T:HasArea >
意味著實(shí)現(xiàn) HasArea 特征的任何類(lèi)型。因?yàn)樘卣鞫x函數(shù)的類(lèi)型簽名,我們可以肯定,任何實(shí)現(xiàn) HasArea 的類(lèi)型都會(huì)有.area()方法。
這里有一個(gè)擴(kuò)展的例子展示它是如何工作的:
trait HasArea {
fn area(&self) -> f64;
}
struct Circle {
x: f64,
y: f64,
radius: f64,
}
impl HasArea for Circle {
fn area(&self) -> f64 {
std::f64::consts::PI * (self.radius * self.radius)
}
}
struct Square {
x: f64,
y: f64,
side: f64,
}
impl HasArea for Square {
fn area(&self) -> f64 {
self.side * self.side
}
}
fn print_area<T: HasArea>(shape: T) {
println!("This shape has an area of {}", shape.area());
}
fn main() {
let c = Circle {
x: 0.0f64,
y: 0.0f64,
radius: 1.0f64,
};
let s = Square {
x: 0.0f64,
y: 0.0f64,
side: 1.0f64,
};
print_area(c);
print_area(s);
}
這個(gè)程序輸出:
This shape has an area of 3.141593
This shape has an area of 1
如您所見(jiàn),print_area 現(xiàn)在是通用的,同時(shí)也確保我們通過(guò)了正確的類(lèi)型。如果我們通過(guò)一個(gè)錯(cuò)誤的類(lèi)型:
print_area(5);
我們會(huì)得到一個(gè)編譯錯(cuò)誤:
error: failed to find an implementation of trait main::HasArea for int
到目前為止,我們只添加特征實(shí)現(xiàn)結(jié)構(gòu),但您可以實(shí)現(xiàn)任何類(lèi)型的特征。所以從技術(shù)上講,我們可以為 i32 實(shí)現(xiàn) HasArea:
trait HasArea {
fn area(&self) -> f64;
}
impl HasArea for i32 {
fn area(&self) -> f64 {
println!("this is silly");
*self as f64
}
}
5.area();
我們一般認(rèn)為用這種基本類(lèi)型來(lái)實(shí)現(xiàn)方法是一種不夠好的風(fēng)格,即便這種實(shí)現(xiàn)方法是可行的。
這可能看起來(lái)比較粗糙,但是有兩個(gè)其他的限制來(lái)控制特征的實(shí)現(xiàn),防止失控。首先,如果特征不是在你的范圍中定義的,那么不適用。下面是一個(gè)例子:標(biāo)準(zhǔn)庫(kù)提供了編寫(xiě)特征,這個(gè)特征給文件的輸入輸出增加了額外的功能。默認(rèn)情況下,文件并不會(huì)有自己的方法:
let mut f = std::fs::File::open("foo.txt").ok().expect("Couldn’t open foo.txt");
let result = f.write("whatever".as_bytes());
這里有一個(gè)錯(cuò)誤:
error: type `std::fs::File` does not implement any method in scope named `write`
let result = f.write(b"whatever");
我們需要先使用編寫(xiě)特征:
use std::io::Write;
let mut f = std::fs::File::open("foo.txt").ok().expect("Couldn’t open foo.txt");
let result = f.write("whatever".as_bytes());
此時(shí)編譯沒(méi)有出現(xiàn)錯(cuò)誤。
這意味著,即使有人做了壞事比如向 int 添加方法,也不會(huì)影響你,除非你使用這個(gè)特征。
實(shí)現(xiàn)特征還有其他限制。你為特征或類(lèi)型所寫(xiě)的 impl 必須由你來(lái)定義。所以,我們可以實(shí)現(xiàn) HasArea 類(lèi)型等,因?yàn)?HasArea 是我們的代碼。但是如果我們?cè)噲D實(shí)現(xiàn)浮點(diǎn)型,它是 Rust 為 i32 所提供的特征,是不可能的,因?yàn)闊o(wú)論特征還是類(lèi)型都不在我們的代碼里面。
最后關(guān)于特征:通用函數(shù)特征的捆綁使用 “monomorphization”(mono:一個(gè),morph:形式),所以他們都是靜態(tài)調(diào)用。那意味著什么呢?到特征對(duì)象那一章查看更多細(xì)節(jié)。
如您所看到的,您可以綁定特征與一個(gè)泛型的類(lèi)型參數(shù):
fn foo<T: Clone>(x: T) {
x.clone();
}
如果你需要多個(gè)綁定,您可以使用+:
use std::fmt::Debug;
fn foo<T: Clone + Debug>(x: T) {
x.clone();
println!("{:?}", x);
}
T 現(xiàn)在需要復(fù)制以及調(diào)試。
只用少數(shù)泛型和少量的特征界限來(lái)寫(xiě)函數(shù)并不是太糟糕,但隨著數(shù)量的增加,語(yǔ)法變得越來(lái)越糟糕:
use std::fmt::Debug;
fn foo<T: Clone, K: Clone + Debug>(x: T, y: K) {
x.clone();
y.clone();
println!("{:?}", y);
}
println !(“{:?}”,y);
}
函數(shù)的名稱是在最左邊,參數(shù)列表在最右邊。邊界就以這種方式存在。
Rust 已經(jīng)有了解決方法,這就是所謂的“where 子句”:
use std::fmt::Debug;
fn foo<T: Clone, K: Clone + Debug>(x: T, y: K) {
x.clone();
y.clone();
println!("{:?}", y);
}
fn bar<T, K>(x: T, y: K) where T: Clone, K: Clone + Debug {
x.clone();
y.clone();
println!("{:?}", y);
}
fn main() {
foo("Hello", "world");
bar("Hello", "workd");
}
foo()使用我們之前講解的語(yǔ)法,bar()使用一個(gè) where 子句。所有你需要做的就是定義類(lèi)型參數(shù)時(shí)不要定義邊界,然后在參數(shù)列表之后添加 where 語(yǔ)句。對(duì)于更長(zhǎng)的列表,可以添加空格:
use std::fmt::Debug;
fn bar<T, K>(x: T, y: K)
where T: Clone,
K: Clone + Debug {
x.clone();
y.clone();
println!("{:?}", y);
}
這種靈活性可以使復(fù)雜情況變得清晰。
Where 語(yǔ)句也比簡(jiǎn)單的語(yǔ)法更加強(qiáng)大。例如:
trait ConvertTo<Output> {
fn convert(&self) -> Output;
}
impl ConvertTo<i64> for i32 {
fn convert(&self) -> i64 { *self as i64 }
}
// can be called with T == i32
fn normal<T: ConvertTo<i64>>(x: &T) -> i64 {
x.convert()
}
// can be called with T == i64
fn inverse<T>() -> T
// this is using ConvertTo as if it were "ConvertFrom<i32>"
where i32: ConvertTo<T> {
1i32.convert()
}
這個(gè)例子展了示 where 子句的附加特性:它們?cè)试S范圍內(nèi)的左邊可以是一個(gè)任意的類(lèi)型(在這里是 i32),而不只是一個(gè)普通的類(lèi)型參數(shù)(如 T)。
我們的最后一個(gè)特征的特性應(yīng)包括:默認(rèn)的方法。舉個(gè)簡(jiǎn)單的例子更容易說(shuō)明:
trait Foo {
fn bar(&self);
fn baz(&self) { println!("We called baz."); }
}
Foo 特征的實(shí)現(xiàn)者需要實(shí)現(xiàn) bar()方法,但是他們不需要實(shí)現(xiàn) baz()。他們會(huì)得到這種默認(rèn)行為。如果他們這么選擇的話就可以覆蓋默認(rèn)的情形:
struct UseDefault;
impl Foo for UseDefault {
fn bar(&self) { println!("We called bar."); }
}
struct OverrideDefault;
impl Foo for OverrideDefault {
fn bar(&self) { println!("We called bar."); }
fn baz(&self) { println!("Override baz!"); }
}
let default = UseDefault;
default.baz(); // prints "We called baz."
let over = OverrideDefault;
over.baz(); // prints "Override baz!"
?
有時(shí),想要實(shí)現(xiàn)某個(gè)特征需要先實(shí)現(xiàn)另一個(gè)特征:
trait Foo {
fn foo(&self);
}
trait FooBar : Foo {
fn foobar(&self);
}
FooBar的實(shí)現(xiàn)者也要實(shí)現(xiàn)Foo,像這樣:
struct Baz;
impl Foo for Baz {
fn foo(&self) { println!("foo"); }
}
impl FooBar for Baz {
fn foobar(&self) { println!("foobar"); }
}
如果我們忘記實(shí)現(xiàn) Foo,Rust 會(huì)告訴我們:
error: the trait `main::Foo` is not implemented for the type `main::Baz` [E027]
更多建議: