特征

2018-08-12 22:03 更新

特征

你還記得 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è)特征邊界

如您所看到的,您可以綁定特征與一個(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)試。

Where 子句

只用少數(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)。

默認(rèn)的方法

我們的最后一個(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]
以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)