定義Struct的方法

Struct就像面向對象的類一樣,Rust允許為Struct定義實例方法和關聯方法,實例方法可被所有實例對象訪問調用,關聯方法類似於其他語言中的類方法或靜態方法。

定義Struct的方法的語法為impl Struct_Name {},所有方法定義在大括號中。

定義Struct的實例方法

實例方法是所有實例對象可訪問、調用的方法。

例如:

struct Rectangle{
  width: u32,
  height: u32,
}

impl Rectangle {
  fn area(&self) -> u32 {
    self.width * self.height
  }

  fn perimeter(&self) -> u32 {
    (self.width + self.height) * 2
  }
}

fn main() {
  let rect1 = Rectangle{width: 30, height: 50};
  println!("{},{}", rect1.area(), rect1.perimeter());
}

也可以將方法定義在多個impl Struct_Name {}中。如下:


#![allow(unused)]
fn main() {
impl Rectangle {
  fn area(&self) -> u32 {
    self.width * self.height
  }

  fn perimeter(&self) -> u32 {
    (self.width + self.height) * 2
  }
}

impl Rectangle {
  fn include(&self, other: &Rectangle) -> bool {
    self.width > other.width && self.height > other.height
  }
}
}

所有Struct的實例方法的第一個參數都是self(的不同形式)。self表示調用方法時的Struct實例對象(如rect1.area()時,self就是rect1)。有如下幾種self形式:

  • fn f(self):當obj.f()時,轉移obj的所有權,調用f方法之後,obj將無效
  • fn f(&self):當obj.f()時,借用而非轉移obj的只讀權,方法內部不可修改obj的屬性,調用f方法之後,obj依然可用
  • fn f(&mut self):當obj.f()時,借用obj的可寫權,方法內部可修改obj的屬性,調用f方法之後,obj依然可用

定義方法時很少使用第一種形式fn f(self),因為這會使得調用方法後對象立即消失。但有時候也能派上場,例如可用於替換對象:調用方法後原對象消失,但返回另一個替換後的對象。

如果仔細觀察的話,會發現方法的第一個參數self(或其他形式)沒有指定類型。實際上,在方法的定義中,self的類型為Self(首字母大寫)。例如,為Rectangle定義方法時,Self類型就是Rectangle類型。因此,下面幾種定義方法的方式是等價的:


#![allow(unused)]
fn main() {
fn f(self)
fn f(self: Self)

fn f(&self)
fn f(self: &Self)

fn f(&mut self)
fn f(self: &mut Self)
}

Rust的自動引用和解引用

在C/C++語言中,有兩個不同的運算符來調用方法:.直接在對象上調用方法,->在一個對象指針上調用方法,這時需要先解引用(dereference)指針。

換句話說,如果obj是一個指針,那麼obj->something()就像(*obj).something()一樣。更典型的是Perl,Perl的對象總是引用類型,因此它調用方法時總是使用obj->m()形式。

Rust不會自動引用或自動解除引用,但有例外:當使用.運算符和比較操作符(如= > >=)時,Rust會自動創建引用和解引用,並且會盡可能地解除多層引用:

  • (1).方法調用v.f()會自動解除引用或創建引用
  • (2).屬性訪問p.namep.0會自動解除引用
  • (3).比較操作符的兩端如果都是引用類型,則自動解除引用
  • (4).能自動解除的引用包括普通引用&xBox<T>Rc<T>

對於(1),方法調用時的自動引用和自動解除引用,它是這樣工作的:當使用ob.something()調用方法時,Rust會根據所調用方法的簽名進行推斷(即根據方法的接收者self參數的形式進行推斷),然後自動為object添加&, &mut來創建引用或添加*來自動解除引用,其目的是讓obj與方法簽名相匹配。

也就是說,當distance方法的第一個形參是&self&mut self時,下面代碼是等價的,但第一行看起來簡潔的多:


#![allow(unused)]
fn main() {
p1.distance(&p2); 
(&p1).distance(&p2);
}

關聯函數(associate functions)

關聯函數是指第一個參數不是self(的各種形式)但和Struct有關聯關係的函數。關聯方法類似於其他語言中類方法或靜態方法的概念。

調用關聯方法的語法StructName::func()。例如,String::from()就是在調用String的關聯方法from()。

例如,可以定義一個專門用於構造實例對象的關聯函數new。

struct Rectangle {
  width: u32,
  height: u32,
}

impl Rectangle {
  // 關聯方法new:構造Rectangle的實例對象
  fn new(width: u32, height: u32) -> Rectangle {
    Rectangle { width, height }
  }
}

impl Rectangle {
  fn area(&self) -> u32 { self.width * self.height }
}

fn main() {
  // 調用關聯方法
  let rect1 = Rectangle::new(30, 50);
  let rect2 = Rectangle::new(20, 50);
  println!("{}", rect1.area());
  println!("{}", rect2.area());
}

實際上,實例方法也屬於關聯方法,也可以採用關聯方法的形式去調用,只不過這時需要手動傳遞第一個self參數。例如:


#![allow(unused)]
fn main() {
// 調用Rectangle的area方法,並傳遞參數&self
Rectangle::area(  &rect1  );
}