Rust Trait
Rust trait 是Rust語言的一個特性(性狀),它描述了它可以提供的每種類型的功能。
性狀類似於其他語言中定義的接口的特徵。
性狀是一種對方法簽名進行分組以定義一組行爲的方法。
使用trait
關鍵字定義性狀。
trait
的語法:
trait trait_name
//body of the trait.
在上面的例子中,聲明特徵後跟特徵(性狀)名稱。 在大括號內,聲明方法簽名以描述實現特徵的類型的行爲。
下面來看一個簡單的例子:
struct Triangle
{
base : f64,
height : f64,
}
trait HasArea
{
fn area(&self)->f64;
}
impl HasArea for Triangle
{
fn area(&self)->f64
{
0.5*(self.base*self.height)
}
}
fn main()
{
let a = Triangle{base:10.5,height:17.4};
let triangle_area = a.area();
println!("Area of a triangle is {}",triangle_area);
}
執行上面示例代碼,得到以下結果 -
Area of a triangle is 91.35
在上面的例子中,聲明瞭一個HasArea
性狀,其中包含area()
函數的聲明。 HasArea
是在Triangle
類型上實現的。 通過使用結構的實例,即a.area()
簡單地調用area()
函數。
性狀作爲參數
特徵(性狀)也可以用作許多不同類型的參數。
上面的例子實現了HasArea
性狀,它包含了area()
函數的定義。 可以定義調用area()
函數的calculate_area()
函數,並使用實現HasArea
特徵的類型的實例調用area()
函數。
下面來來看看語法:
fn calculate_area(item : impl HasArea)
println!("Area of the triangle is : {}",item.area());
}
性狀限制了通用函數
性狀很有用,因爲它們描述了不同方法的行爲。 但是,通用函數不遵循此約束。 通過一個簡單的場景來理解這一點:
fn calculate_area<T>( item : T)
println!(?Area of a triangle is {}?, item.area());
在上面的例子中,Rust編譯器拋出「沒有找到類型爲T
的方法的錯誤」。 如果將性狀綁定到泛型T
,則可以解決以下錯誤:
fn calculate_area<T : HasArea> (item : T)
{
println!("Area of a triangle is {} ",item.area());
}
在上面的例子中,<T:HasArea>
表示T
可以是任何實現HasArea
性狀的類型。 Rust編譯器知道任何實現HasArea
性狀的類型都有一個area()
函數。
下面來看一個簡單的例子:
trait HasArea
{
fn area(&self)->f64;
}
struct Triangle
{
base : f64,
height : f64,
}
impl HasArea for Triangle
{
fn area(&self)->f64
{
0.5*(self.base*self.height)
}
}
struct Square
{
side : f64,
}
impl HasArea for Square
{
fn area(&self)->f64
{
self.side*self.side
}
}
fn calculate_area<T : HasArea>(item : T)
{
println!("Area is : {}",item.area());
}
fn main()
{
let a = Triangle{base:10.5,height:17.4};
let b = Square{side : 4.5};
calculate_area(a);
calculate_area(b);
}
執行上面示例代碼,得到以下結果 -
Area is : 91.35
Area is : 20.25
在上面的例子中,calculate_area()
函數在T
上是通用的。
實施性狀的規則
實現性狀有兩個限制:
- 如果範圍中未定義性狀,則無法在任何數據類型上實現該性狀。
下面來看一個簡單的例子:
use::std::fs::File;
fn main()
{
let mut f = File::create("hello.txt");
let str = "Yiibai";
let result = f.write(str);
}
執行上面示例代碼,得到以下結果 -
error : no method named 'write' found.
let result = f.write(str);
在上面的例子中,Rust編譯器拋出一個錯誤,即"no method named 'write' found"
爲use::std::fs::File;
, 命名空間不包含write()
方法。 因此,需要使用Write trait
來刪除編譯錯誤。
- 正在實現的性狀必須定義。 例如:如果定義
HasArea
性狀,那麼要爲i32
類型實現這個性狀。 但是,無法爲類型i32
實現Rust定義的toString
性狀,因爲類型和性狀沒有在包中定義。
多個性狀界限
使用'+'
運算符。
如果想綁定多個性狀,可使用+
運算符。
下面來看一個簡單的例子:
use std::fmt::{Debug, Display};
fn compare_prints<T: Debug + Display>(t: &T)
{
println!("Debug: '{:?}'", t);
println!("Display: '{}'", t);
}
fn main() {
let string = "Yiibai";
compare_prints(&string);
}
執行上面示例代碼,輸出結果如下 -
Debug: ' "Yiibai"'
Display: ' Yiibai'
在上面的示例中,Display
和Debug
特性通過使用+
運算符限制爲類型T
。
使用where
子句。
- 使用出現在括號
{
之前的where
子句來編寫綁定。 -
where
子句也可以應用於任意類型。 - 當使用
where
子句時,它使語法比普通語法更具表現力。
如下代碼 -
fn fun<T: Display+Debug, V: Clone+Debug>(t:T,v:V)->i32
//block of code;
在上述情況下使用where
時:
fn fun<T, V>(t:T, v:V)->i32
where T : Display+ Debug,
V : Clone+ Debug
//block of code;
在上面的例子中,使用where
子句的第二種情況使程序更具表現力和可讀性。
下面來看看一個簡單的例子:
trait Perimeter
{
fn a(&self)->f64;
}
struct Square
{
side : f64,
}
impl Perimeter for Square
{
fn a(&self)->f64
{
4.0*self.side
}
}
struct Rectangle
{
length : f64,
breadth : f64,
}
impl Perimeter for Rectangle
{
fn a(&self)->f64
{
2.0*(self.length+self.breadth)
}
}
fn print_perimeter<Square,Rectangle>(s:Square,r:Rectangle)
where Square : Perimeter,
Rectangle : Perimeter
{
let r1 = s.a();
let r2 = r.a();
println!("Perimeter of a square is {}",r1);
println!("Perimeter of a rectangle is {}",r2);
}
fn main()
{
let sq = Square{side : 6.2};
let rect = Rectangle{length : 3.2,breadth:5.6};
print_perimeter(sq,rect);
}
執行上面示例代碼,得到以下結果 -
Perimeter of a square is 24.8
Perimeter of a rectangle is 17.6
默認方法
可以將默認方法添加到性狀定義的方法定義爲已知。
示例代碼:
trait Sample
fn a(&self);
fn b(&self)
{
println!("Print b");
}
在上面的例子中,默認行爲被添加到性狀定義中。 還可以覆蓋默認行爲。下面通過一個例子看看這個場景:
trait Sample
{
fn a(&self);
fn b(&self)
{
println!("Print b");
}
}
struct Example
{
a:i32,
b:i32,
}
impl Sample for Example
{
fn a(&self)
{
println!("Value of a is {}",self.a);
}
fn b(&self)
{
println!("Value of b is {}",self.b);
}
}
fn main()
{
let r = Example{a:5,b:7};
r.a();
r.b();
}
執行上面示例代碼,得到以下結果 -
Value of a is : 5
Value of b is : 7
在上面的例子中,b()
函數的行爲是在被覆蓋的性狀中定義的。 因此得出結論,可覆蓋性狀中定義的方法。
繼承
從另一個性狀派生的性狀稱爲繼承。 有時,有必要實現另一個性狀的性狀。 如果想從’A’性狀繼承’B’性狀,那麼它看起來像:
trait B : A;
參考以下一段完整的代碼 -
trait A
{
fn f(&self);
}
trait B : A
{
fn t(&self);
}
struct Example
{
first : String,
second : String,
}
impl A for Example
{
fn f(&self)
{
print!("{} ",self.first);
}
}
impl B for Example
{
fn t(&self)
{
print!("{}",self.second);
}
}
fn main()
{
let s = Example{first:String::from("Yiibai"),second:String::from("tutorial")};
s.f();
s.t();
}
執行上面示例代碼,得到以下結果 -
Yiibai tutorial
在上面的例子中,程序實現’B’性狀。 因此,它還需要實現’A’性狀。 如果程序沒有實現’A’性狀,則Rust編譯器會拋出錯誤。