方法

参考

  • 固有实现
  • Trait实现
    • Trait实现一致性
  • 泛型实现

方法定义

  1. [derive(Debug)]
  2. struct Rectangle {
  3. width: u32,
  4. height: u32,
  5. }
  6. impl Rectangle {
  7. fn area(&self) -> u32 {
  8. self.width * self.height
  9. }
  10. }
  11. fn main() {
  12. let rect1 = Rectangle { width: 30, height: 50 };
  13. println!(
  14. "The area of the rectangle is {} square pixels.",
  15. rect1.area()
  16. );
  17. }

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,就像相比获取所有权你应该更倾向于借用,同样相比获取可变引用更倾向于不可变引用一样。这是一个三种变体的例子:

  1. struct Circle {
  2. x: f64,
  3. y: f64,
  4. radius: f64,
  5. }
  6. impl Circle {
  7. fn reference(&self) {
  8. println!("taking self by reference!");
  9. }
  10. fn mutable_reference(&mut self) {
  11. println!("taking self by mutable reference!");
  12. }
  13. fn takes_ownership(self) {
  14. println!("taking ownership of self!");
  15. }
  16. }

链式方法调用

现在我们知道如何调用方法,例如foo.bar()。那么foo.bar().baz()?我们称这个为“方法链”,我们可以通过返回self来做到这点。

  1. struct Circle {
  2. x: f64,
  3. y: f64,
  4. radius: f64,
  5. }
  6. impl Circle {
  7. fn area(&self) -> f64 {
  8. std::f64::consts::PI * (self.radius * self.radius)
  9. }
  10. fn grow(&self, increment: f64) -> Circle {
  11. Circle { x: self.x, y: self.y, radius: self.radius + increment }
  12. }
  13. }
  14. fn main() {
  15. let c = Circle { x: 0.0, y: 0.0, radius: 2.0 };
  16. println!("{}", c.area());
  17. let d = c.grow(2.0).area();
  18. println!("{}", d);
  19. }

注意返回值:

  1. struct Circle;
  2. impl Circle {
  3. fn grow(&self, increment: f64) -> Circle {
  4. Circle
  5. }
  6. }

我们看到我们返回了一个Circle。通过这个函数,我们可以增长一个圆的面积到任意大小。

关联函数

impl 块的另一个有用的功能是:允许在impl块中定义 不 以 self 作为参数的函数。这被称为 关联函数,因为它们与结构体相关联。即便如此它们仍是函数而不是方法,因为它们并不作用于一个结构体的实例。关联函数经常被用作返回一个结构体新实例的构造函数。使用结构体名和 ::语法来调用这个关联函数,::语法用于关联函数和模块创建的命名空间。

  1. struct Circle {
  2. x: f64,
  3. y: f64,
  4. radius: f64,
  5. }
  6. impl Circle {
  7. fn new(x: f64, y: f64, radius: f64) -> Circle {
  8. Circle {
  9. x: x,
  10. y: y,
  11. radius: radius,
  12. }
  13. }
  14. }
  15. fn main() {
  16. let c = Circle::new(0.0, 0.0, 2.0);
  17. }

这个关联函数associated function)为我们构建了一个新的Circle。注意静态函数是通过Struct::method()语法调用的,而不是ref.method()语法。

创建者模式

我们可以创建圆,不过我们只允许他们设置他们关心的属性。否则,xy将是0.0,并且radius将是1.0。Rust 并没有方法重载,命名参数或者可变参数。我们利用创建者模式来代替。它看起像这样:

  1. struct Circle {
  2. x: f64,
  3. y: f64,
  4. radius: f64,
  5. }
  6. impl Circle {
  7. fn area(&self) -> f64 {
  8. std::f64::consts::PI * (self.radius * self.radius)
  9. }
  10. }
  11. struct CircleBuilder {
  12. x: f64,
  13. y: f64,
  14. radius: f64,
  15. }
  16. impl CircleBuilder {
  17. fn new() -> CircleBuilder {
  18. CircleBuilder { x: 0.0, y: 0.0, radius: 1.0, }
  19. }
  20. fn x(&mut self, coordinate: f64) -> &mut CircleBuilder {
  21. self.x = coordinate;
  22. self
  23. }
  24. fn y(&mut self, coordinate: f64) -> &mut CircleBuilder {
  25. self.y = coordinate;
  26. self
  27. }
  28. fn radius(&mut self, radius: f64) -> &mut CircleBuilder {
  29. self.radius = radius;
  30. self
  31. }
  32. fn finalize(&self) -> Circle {
  33. Circle { x: self.x, y: self.y, radius: self.radius }
  34. }
  35. }
  36. fn main() {
  37. let c = CircleBuilder::new()
  38. .x(1.0)
  39. .y(2.0)
  40. .radius(2.0)
  41. .finalize();
  42. println!("area: {}", c.area());
  43. println!("x: {}", c.x);
  44. println!("y: {}", c.y);
  45. }

我们在这里又声明了一个结构体,CircleBuilder。我们给它定义了一个创建者函数。我们也在Circle中定义了area()方法。我们还定义了另一个方法CircleBuilder: finalize()。这个方法从构造器中创建了我们最后的Circle。现在我们使用类型系统来强化我们的考虑:我们可以用CircleBuilder来强制生成我们需要的Circle

通用函数调用语法

有时,函数可能有相同的名字。就像下面这些代码:

  1. trait Foo {
  2. fn f(&self);
  3. }
  4. trait Bar {
  5. fn f(&self);
  6. }
  7. struct Baz;
  8. impl Foo for Baz {
  9. fn f(&self) { println!("Baz’s impl of Foo"); }
  10. }
  11. impl Bar for Baz {
  12. fn f(&self) { println!("Baz’s impl of Bar"); }
  13. }
  14. let b = Baz;

如果我们尝试调用b.f(),我们会得到一个错误:

  1. error: multiple applicable methods in scope [E0034]
  2. b.f();
  3. ^~~
  4. note: candidate #1 is defined in an impl of the trait `main::Foo` for the type
  5. `main::Baz`
  6. fn f(&self) { println!("Baz’s impl of Foo"); }
  7. ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  8. note: candidate #2 is defined in an impl of the trait `main::Bar` for the type
  9. `main::Baz`
  10. fn f(&self) { println!("Baz’s impl of Bar"); }
  11. ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

我们需要一个区分我们需要调用哪一函数的方法。这个功能叫做“通用函数调用语法”(universal function call syntax),这看起来像这样:

  1. trait Foo {
  2. fn f(&self);
  3. }
  4. trait Bar {
  5. fn f(&self);
  6. }
  7. struct Baz;
  8. impl Foo for Baz {
  9. fn f(&self) { println!("Baz’s impl of Foo"); }
  10. }
  11. impl Bar for Baz {
  12. fn f(&self) { println!("Baz’s impl of Bar"); }
  13. }
  14. let b = Baz;
  15. Foo::f(&b);
  16. Bar::f(&b);

让我们拆开来看。

  1. Foo::
  2. Bar::

调用的这一半是两个traits的类型:FooBar。这样实际上就区分了这两者:Rust调用你使用的trait里面的方法。

  1. f(&b)

当我们使用方法语法调用像b.f()这样的方法时,如果f()需要&self,Rust实际上会自动地把b借用为&self。而在这个例子中,Rust并不会这么做,所以我们需要显式地传递一个&b

刚才讨论的通用函数调用语法的形式:

  1. Trait::method(args);

上面的形式其实是一种缩写。这是在一些情况下需要使用的扩展形式:

  1. <Type as Trait>::method(args);

<>::语法是一个提供类型提示的方法。类型位于<>中。在这个例子中,类型是Type as Trait,表示我们想要methodTrait版本被调用。在没有二义时as Trait部分是可选的。尖括号也是一样。因此上面的形式就是一种缩写的形式。

这是一个使用较长形式的例子。

  1. trait Foo {
  2. fn foo() -> i32;
  3. }
  4. struct Bar;
  5. impl Bar {
  6. fn foo() -> i32 {
  7. 20
  8. }
  9. }
  10. impl Foo for Bar {
  11. fn foo() -> i32 {
  12. 10
  13. }
  14. }
  15. fn main() {
  16. assert_eq!(10, <Bar as Foo>::foo());
  17. assert_eq!(20, Bar::foo());
  18. }

使用尖括号语法让你可以调用指定trait的方法而不是继承到的那个。