橘子味的心
标题: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'

在上面的示例中,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("Yiibai"),second:String::from("tutorial")};  
  s.f();  
  s.t();  
}

执行上面示例代码,得到以下结果 -

Yiibai tutorial

在上面的例子中,程序实现’B’性状。 因此,它还需要实现’A’性状。 如果程序没有实现’A’性状,则Rust编译器会抛出错误。


分类