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 = "zyiz"; 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 = "zyiz"; compare_prints(&string); }
执行上面示例代码,输出结果如下 -
Debug: ' "zyiz"' Display: ' zyiz'
在上面的示例中,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("zyiz"),second:String::from("tutorial")}; s.f(); s.t(); }
执行上面示例代码,得到以下结果 -
zyiz tutorial
在上面的例子中,程序实现’B’性状。 因此,它还需要实现’A’性状。 如果程序没有实现’A’性状,则Rust编译器会抛出错误。