橘子味的心
标题:Rust Trait

Rust trait 是Rust语言的一个特性(性状),它描述了它可以提供的每种类型的功能。
性状类似于其他语言中定义的接口的特征。
性状是一种对方法签名进行分组以定义一组行为的方法。
使用trait关键字定义性状。

trait的语法:

  1. trait trait_name
  2. //body of the trait.
  3. Rust

在上面的例子中,声明特征后跟特征(性状)名称。 在大括号内,声明方法签名以描述实现特征的类型的行为。

下面来看一个简单的例子:

  1. struct Triangle
  2. {
  3. base : f64,
  4. height : f64,
  5. }
  6. trait HasArea
  7. {
  8. fn area(&self)->f64;
  9. }
  10.  
  11. impl HasArea for Triangle
  12. {
  13. fn area(&self)->f64
  14. {
  15. 0.5*(self.base*self.height)
  16. }
  17. }
  18. fn main()
  19. {
  20. let a = Triangle{base:10.5,height:17.4};
  21. let triangle_area = a.area();
  22. println!("Area of a triangle is {}",triangle_area);
  23. }
  24. Rust

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

  1. Area of a triangle is 91.35
  2. Shell

在上面的例子中,声明了一个HasArea性状,其中包含area()函数的声明。 HasArea是在Triangle类型上实现的。 通过使用结构的实例,即a.area()简单地调用area()函数。

性状作为参数

特征(性状)也可以用作许多不同类型的参数。

上面的例子实现了HasArea性状,它包含了area()函数的定义。 可以定义调用area()函数的calculate_area()函数,并使用实现HasArea特征的类型的实例调用area()函数。

下面来来看看语法:

  1. fn calculate_area(item : impl HasArea)
  2. println!("Area of the triangle is : {}",item.area());
  3. }
  4. Rust

性状限制了通用函数

性状很有用,因为它们描述了不同方法的行为。 但是,通用函数不遵循此约束。 通过一个简单的场景来理解这一点:

  1. fn calculate_area<T>( item : T)
  2. println!(?Area of a triangle is {}?, item.area());
  3. Rust

在上面的例子中,Rust编译器抛出“没有找到类型为T的方法的错误”。 如果将性状绑定到泛型T,则可以解决以下错误:

  1. fn calculate_area<T : HasArea> (item : T)
  2. {
  3. println!("Area of a triangle is {} ",item.area());
  4. }
  5. Rust

在上面的例子中,<T:HasArea>表示T可以是任何实现HasArea性状的类型。 Rust编译器知道任何实现HasArea性状的类型都有一个area()函数。

下面来看一个简单的例子:

  1. trait HasArea
  2. {
  3. fn area(&self)->f64;
  4. }
  5. struct Triangle
  6. {
  7. base : f64,
  8. height : f64,
  9. }
  10.  
  11. impl HasArea for Triangle
  12. {
  13. fn area(&self)->f64
  14. {
  15. 0.5*(self.base*self.height)
  16. }
  17. }
  18. struct Square
  19. {
  20. side : f64,
  21. }
  22.  
  23. impl HasArea for Square
  24. {
  25. fn area(&self)->f64
  26. {
  27. self.side*self.side
  28. }
  29. }
  30. fn calculate_area<T : HasArea>(item : T)
  31. {
  32. println!("Area is : {}",item.area());
  33. }
  34.  
  35. fn main()
  36. {
  37. let a = Triangle{base:10.5,height:17.4};
  38. let b = Square{side : 4.5};
  39. calculate_area(a);
  40. calculate_area(b);
  41. }
  42. Rust

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

  1. Area is : 91.35
  2. Area is : 20.25
  3. Shell

在上面的例子中,calculate_area()函数在T上是通用的。

实施性状的规则

实现性状有两个限制:

  • 如果范围中未定义性状,则无法在任何数据类型上实现该性状。

下面来看一个简单的例子:

  1. use::std::fs::File;
  2. fn main()
  3. {
  4. let mut f = File::create("hello.txt");
  5. let str = "Yiibai";
  6. let result = f.write(str);
  7. }
  8. Rust

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

  1. error : no method named 'write' found.
  2. let result = f.write(str);
  3. Shell

在上面的例子中,Rust编译器抛出一个错误,即"no method named 'write' found"use::std::fs::File;, 命名空间不包含write()方法。 因此,需要使用Write trait来删除编译错误。

  • 正在实现的性状必须定义。 例如:如果定义HasArea性状,那么要为i32类型实现这个性状。 但是,无法为类型i32实现Rust定义的toString性状,因为类型和性状没有在包中定义。

多个性状界限

使用'+'运算符。

如果想绑定多个性状,可使用+运算符。

下面来看一个简单的例子:

  1. use std::fmt::{Debug, Display};
  2. fn compare_prints<T: Debug + Display>(t: &T)
  3. {
  4. println!("Debug: '{:?}'", t);
  5. println!("Display: '{}'", t);
  6. }
  7.  
  8. fn main() {
  9. let string = "Yiibai";
  10. compare_prints(&string);
  11. }
  12. Rust

执行上面示例代码,输出结果如下 -

  1. Debug: ' "Yiibai"'
  2. Display: ' Yiibai'
  3. Shell

在上面的示例中,DisplayDebug特性通过使用+运算符限制为类型T

使用where子句。

  • 使用出现在括号{之前的where子句来编写绑定。
  • where子句也可以应用于任意类型。
  • 当使用where子句时,它使语法比普通语法更具表现力。

如下代码 -

  1. fn fun<T: Display+Debug, V: Clone+Debug>(t:T,v:V)->i32
  2. //block of code;
  3. Rust

在上述情况下使用where时:

  1. fn fun<T, V>(t:T, v:V)->i32
  2. where T : Display+ Debug,
  3. V : Clone+ Debug
  4.  
  5. //block of code;
  6. Rust

在上面的例子中,使用where子句的第二种情况使程序更具表现力和可读性。

下面来看看一个简单的例子:

  1. trait Perimeter
  2. {
  3. fn a(&self)->f64;
  4. }
  5. struct Square
  6. {
  7. side : f64,
  8. }
  9. impl Perimeter for Square
  10. {
  11. fn a(&self)->f64
  12. {
  13. 4.0*self.side
  14. }
  15. }
  16. struct Rectangle
  17. {
  18. length : f64,
  19. breadth : f64,
  20. }
  21. impl Perimeter for Rectangle
  22.  
  23. {
  24. fn a(&self)->f64
  25. {
  26. 2.0*(self.length+self.breadth)
  27. }
  28. }
  29. fn print_perimeter<Square,Rectangle>(s:Square,r:Rectangle)
  30. where Square : Perimeter,
  31. Rectangle : Perimeter
  32. {
  33. let r1 = s.a();
  34. let r2 = r.a();
  35. println!("Perimeter of a square is {}",r1);
  36. println!("Perimeter of a rectangle is {}",r2);
  37. }
  38. fn main()
  39. {
  40. let sq = Square{side : 6.2};
  41. let rect = Rectangle{length : 3.2,breadth:5.6};
  42. print_perimeter(sq,rect);
  43. }
  44. Rust

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

  1. Perimeter of a square is 24.8
  2. Perimeter of a rectangle is 17.6
  3. Shell

默认方法

可以将默认方法添加到性状定义的方法定义为已知。
示例代码:

  1. trait Sample
  2.  
  3. fn a(&self);
  4. fn b(&self)
  5. {
  6. println!("Print b");
  7. }
  8. Rust

在上面的例子中,默认行为被添加到性状定义中。 还可以覆盖默认行为。下面通过一个例子看看这个场景:

  1. trait Sample
  2. {
  3. fn a(&self);
  4. fn b(&self)
  5. {
  6. println!("Print b");
  7. }
  8. }
  9.  
  10. struct Example
  11. {
  12. a:i32,
  13. b:i32,
  14. }
  15.  
  16.  
  17.  
  18. impl Sample for Example
  19. {
  20. fn a(&self)
  21. {
  22. println!("Value of a is {}",self.a);
  23. }
  24.  
  25. fn b(&self)
  26. {
  27. println!("Value of b is {}",self.b);
  28. }
  29. }
  30. fn main()
  31. {
  32. let r = Example{a:5,b:7};
  33. r.a();
  34. r.b();
  35. }
  36. Rust

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

  1. Value of a is : 5
  2. Value of b is : 7
  3. Shell

在上面的例子中,b()函数的行为是在被覆盖的性状中定义的。 因此得出结论,可覆盖性状中定义的方法。

继承

从另一个性状派生的性状称为继承。 有时,有必要实现另一个性状的性状。 如果想从’A’性状继承’B’性状,那么它看起来像:

  1. trait B : A;
  2. Rust

参考以下一段完整的代码 -

  1. trait A
  2. {
  3. fn f(&self);
  4. }
  5. trait B : A
  6. {
  7. fn t(&self);
  8. }
  9. struct Example
  10. {
  11. first : String,
  12. second : String,
  13. }
  14. impl A for Example
  15. {
  16. fn f(&self)
  17. {
  18.  
  19. print!("{} ",self.first);
  20. }
  21.  
  22. }
  23. impl B for Example
  24. {
  25. fn t(&self)
  26. {
  27. print!("{}",self.second);
  28. }
  29. }
  30. fn main()
  31. {
  32. let s = Example{first:String::from("Yiibai"),second:String::from("tutorial")};
  33. s.f();
  34. s.t();
  35. }
  36. Rust

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

  1. Yiibai tutorial
  2. Shell

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