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