方法
- 固有实现
Trait实现Trait实现一致性
- 泛型实现
方法定义
[derive(Debug)]struct Rectangle {width: u32,height: u32,}impl Rectangle {fn area(&self) -> u32 {self.width * self.height}}fn main() {let rect1 = Rectangle { width: 30, height: 50 };println!("The area of the rectangle is {} square pixels.",rect1.area());}
在 area 的签名中,开始使用 &self 来替代 rectangle: &Rectangle,因为该方法位于 impl Rectangle 上下文中所以 Rust 知道 self 的类型是 Rectangle。注意仍然需要在 self 前面加上&,就像 &Rectangle 一样。方法可以选择获取 self 的所有权,或者像我们这里一样不可变地借用 self,或者可变地借用 self,就跟其他别的参数一样。
&self。它有3种变体:self,&self和&mut self。这里选择 &self 跟在函数版本中使用 &Rectangle 出于同样的理由:我们并不想获取所有权,只希望能够读取结构体中的数据,而不是写入。如果想要在方法中改变调用方法的实例,需要将第一个参数改为&mut self。通过仅仅使用 self 作为第一个参数来使方法获取实例的所有权是很少见的;这种技术通常用在当方法将 self 转换成别的实例的时候,这时我们想要防止调用者在转换之后使用原始的实例。
我们应该更多使用&self,就像相比获取所有权你应该更倾向于借用,同样相比获取可变引用更倾向于不可变引用一样。这是一个三种变体的例子:
struct Circle {x: f64,y: f64,radius: f64,}impl Circle {fn reference(&self) {println!("taking self by reference!");}fn mutable_reference(&mut self) {println!("taking self by mutable reference!");}fn takes_ownership(self) {println!("taking ownership of self!");}}
链式方法调用
现在我们知道如何调用方法,例如foo.bar()。那么foo.bar().baz()?我们称这个为“方法链”,我们可以通过返回self来做到这点。
struct Circle {x: f64,y: f64,radius: f64,}impl Circle {fn area(&self) -> f64 {std::f64::consts::PI * (self.radius * self.radius)}fn grow(&self, increment: f64) -> Circle {Circle { x: self.x, y: self.y, radius: self.radius + increment }}}fn main() {let c = Circle { x: 0.0, y: 0.0, radius: 2.0 };println!("{}", c.area());let d = c.grow(2.0).area();println!("{}", d);}
注意返回值:
struct Circle;impl Circle {fn grow(&self, increment: f64) -> Circle {Circle}}
我们看到我们返回了一个Circle。通过这个函数,我们可以增长一个圆的面积到任意大小。
关联函数
impl 块的另一个有用的功能是:允许在impl块中定义 不 以 self 作为参数的函数。这被称为 关联函数,因为它们与结构体相关联。即便如此它们仍是函数而不是方法,因为它们并不作用于一个结构体的实例。关联函数经常被用作返回一个结构体新实例的构造函数。使用结构体名和 ::语法来调用这个关联函数,::语法用于关联函数和模块创建的命名空间。
struct Circle {x: f64,y: f64,radius: f64,}impl Circle {fn new(x: f64, y: f64, radius: f64) -> Circle {Circle {x: x,y: y,radius: radius,}}}fn main() {let c = Circle::new(0.0, 0.0, 2.0);}
这个关联函数(associated function)为我们构建了一个新的Circle。注意静态函数是通过Struct::method()语法调用的,而不是ref.method()语法。
创建者模式
我们可以创建圆,不过我们只允许他们设置他们关心的属性。否则,x和y将是0.0,并且radius将是1.0。Rust 并没有方法重载,命名参数或者可变参数。我们利用创建者模式来代替。它看起像这样:
struct Circle {x: f64,y: f64,radius: f64,}impl Circle {fn area(&self) -> f64 {std::f64::consts::PI * (self.radius * self.radius)}}struct CircleBuilder {x: f64,y: f64,radius: f64,}impl CircleBuilder {fn new() -> CircleBuilder {CircleBuilder { x: 0.0, y: 0.0, radius: 1.0, }}fn x(&mut self, coordinate: f64) -> &mut CircleBuilder {self.x = coordinate;self}fn y(&mut self, coordinate: f64) -> &mut CircleBuilder {self.y = coordinate;self}fn radius(&mut self, radius: f64) -> &mut CircleBuilder {self.radius = radius;self}fn finalize(&self) -> Circle {Circle { x: self.x, y: self.y, radius: self.radius }}}fn main() {let c = CircleBuilder::new().x(1.0).y(2.0).radius(2.0).finalize();println!("area: {}", c.area());println!("x: {}", c.x);println!("y: {}", c.y);}
我们在这里又声明了一个结构体,CircleBuilder。我们给它定义了一个创建者函数。我们也在Circle中定义了area()方法。我们还定义了另一个方法CircleBuilder: finalize()。这个方法从构造器中创建了我们最后的Circle。现在我们使用类型系统来强化我们的考虑:我们可以用CircleBuilder来强制生成我们需要的Circle。
通用函数调用语法
有时,函数可能有相同的名字。就像下面这些代码:
trait Foo {fn f(&self);}trait Bar {fn f(&self);}struct Baz;impl Foo for Baz {fn f(&self) { println!("Baz’s impl of Foo"); }}impl Bar for Baz {fn f(&self) { println!("Baz’s impl of Bar"); }}let b = Baz;
如果我们尝试调用b.f(),我们会得到一个错误:
error: multiple applicable methods in scope [E0034]b.f();^~~note: candidate #1 is defined in an impl of the trait `main::Foo` for the type`main::Baz`fn f(&self) { println!("Baz’s impl of Foo"); }^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~note: candidate #2 is defined in an impl of the trait `main::Bar` for the type`main::Baz`fn f(&self) { println!("Baz’s impl of Bar"); }^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
我们需要一个区分我们需要调用哪一函数的方法。这个功能叫做“通用函数调用语法”(universal function call syntax),这看起来像这样:
trait Foo {fn f(&self);}trait Bar {fn f(&self);}struct Baz;impl Foo for Baz {fn f(&self) { println!("Baz’s impl of Foo"); }}impl Bar for Baz {fn f(&self) { println!("Baz’s impl of Bar"); }}let b = Baz;Foo::f(&b);Bar::f(&b);
让我们拆开来看。
Foo::Bar::
调用的这一半是两个traits的类型:Foo和Bar。这样实际上就区分了这两者:Rust调用你使用的trait里面的方法。
f(&b)
当我们使用方法语法调用像b.f()这样的方法时,如果f()需要&self,Rust实际上会自动地把b借用为&self。而在这个例子中,Rust并不会这么做,所以我们需要显式地传递一个&b。
刚才讨论的通用函数调用语法的形式:
Trait::method(args);
上面的形式其实是一种缩写。这是在一些情况下需要使用的扩展形式:
<Type as Trait>::method(args);
<>::语法是一个提供类型提示的方法。类型位于<>中。在这个例子中,类型是Type as Trait,表示我们想要method的Trait版本被调用。在没有二义时as Trait部分是可选的。尖括号也是一样。因此上面的形式就是一种缩写的形式。
这是一个使用较长形式的例子。
trait Foo {fn foo() -> i32;}struct Bar;impl Bar {fn foo() -> i32 {20}}impl Foo for Bar {fn foo() -> i32 {10}}fn main() {assert_eq!(10, <Bar as Foo>::foo());assert_eq!(20, Bar::foo());}
使用尖括号语法让你可以调用指定trait的方法而不是继承到的那个。
