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 = "zaixian";
  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 = "zaixian";
    compare_prints(&string);
}

執行上面示例代碼,輸出結果如下 -

Debug: ' "zaixian"'
Display: ' zaixian'

在上面的示例中,DisplayDebug特性通過使用+運算符限制為類型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("zaixian"),second:String::from("tutorial")};
  s.f();
  s.t();
}

執行上面示例代碼,得到以下結果 -

zaixian tutorial

在上面的例子中,程式實現’B’性狀。 因此,它還需要實現’A’性狀。 如果程式沒有實現’A’性狀,則Rust編譯器會拋出錯誤。


上一篇: Rust泛型 下一篇: Rust生命週期