1. 方法和函数的区别

        方法与函数十分相似:它们都使用fn关键字及一个名称来进行声明;它们都可以拥有参数和返回值;另外,它们都包含了一段在调用时执行的代码

        但是,方法与函数依然是两个不同的概念,因为方法总是被定义在某个结构体(或者枚举类型trait对象我们会在后面分别介绍它们)的上下文中并且它们的第一个参数永远都是self用于指代调用该方法的结构体实例。 

2. 定义方法 

        现在,让我们把那个以 Rectangle 实例作为参数的 area 函数,改写为定义在 Rectangle 结构体中的 area 方法,如 示例5-13 所示。 

// 示例5-13:在Rectangle结构体中定义area方法

#[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()
    );
}

        为了在 Rectangle 的上下文环境中定义这个函数,我们需要将 area 函数移动到一个由 impl implementation)关键字❶起始的代码块中❷,并把签名中的第一个参数(也是唯一的那个参数)和函数中使用该参数的地方改写为 self

        除此之外,我们还需要把 main 函数中调用 area 函数的地方,用方法调用的语法进行改写。前者是将 rect1 作为参数传入 area 函数,而后者则直接在 Rectangle 实例上调用 area 方法❸。

        方法调用是通过在实例后面加点号,并跟上方法名、括号及可能的参数来实现的。由于方法的声明过程被放置在 impl Rectangle 块中,所以Rust能够将 self 的类型推导为 Rectangle

        也正是因为这样,我们才可以在 area 的签名中使用 &self 来代替 rectangle: &Rectangle。但我们依然需要在 self 之前添加 &,就像 &Rectangle 一样。

        方法可以在声明时选择获取 self 的所有权,也可以像本例一样采用不可变的借用 &self,或者采用可变的借用 &mut self。总之,就像是其他任何普通的参数一样。 

         在这里,选择 &self 签名的原因和之前选择使用 &Rectangle 的原因差不多:我们既不用获得数据的所有权也不需要写入数据,而只需要读取数据即可。

        假如我们想要在调用方法时改变实例的某些数据,那么就需要将第一个参数改写为 &mut self。通常来说,将第一个参数标记为 self 并在调用过程中取得实例的所有权的方法并不常见。

        这种技术有可能会被用于那些需要将 self 转换为其他类型,且在转换后想要阻止调用者访问原始实例的场景。使用方法替代函数不仅能够避免在每个方法的签名中重复编写 self 的类型,还有助于我们组织代码的结构。

        我们可以将某个类型的实例需要的功能放置在同一个 impl 块中,从而避免用户在代码库中盲目地自行搜索它们。

3. 带有更多参数的方法

        现在,让我们通过实现 Rectangle 结构体的第二个方法来继续练习使用这种方法。这次我们要实现的是:检测当前的 Rectangle 实例是否能完整包含传入的另外一个 Rectangle 实例,如果是的话就返回 true,否则返回 false

        也就是说,一旦我们完成了这个方法(can_hold),我们就能像 示例5-14 中所示的那样去使用它。 

// 示例5-14:使用还没有编写好的can_hold方法

fn main() {
    let rect1 = Rectangle { width: 30, height: 50 };
    let rect2 = Rectangle { width: 10, height: 40 };
    let rect3 = Rectangle { width: 60, height: 45 };

    println!("Can rect1 hold rect2? {}", rect1.can_hold(&rect2));
    println!("Can rect1 hold rect3? {}", rect1.can_hold(&rect3));
}

        因为 rect2 的两个维度都要小于 rect1,而 rect3 的宽度要大于 rect1,所以如果一切正常的话,它们应当能够输出如下所示的结果: 

Can rect1 hold rect2? true
Can rect1 hold rect3? false

        因为我们想要定义的是方法,所以我们会把新添加的代码放置到 impl Rectangle 块中。另外,这个名为 can_hold 的方法需要接收另一个 Rectangle 的不可变借用作为参数。

        通过观察调用方法时的代码便可以推断出此处的参数类型:语句 rect1.can_hold(&rect2) 中传入了一个 &rect2,也就是指向 Rectangle 实例 rect2 的不可变借用。

        为了计算包容关系,我们只需要去读取 rect2 的数据(而不是写入,写入意味着需要一个可变借用)。main 函数还应该在调用 can_hold 方法后继续持有 rect2 的所有权,从而使得我们可以在随后的代码中继续使用这个变量。

        can_hold 方法在实现时会依次检查 self 的宽度和长度是否大于传入的 Rectangle 实例的宽度和长度,并返回一个布尔类型作为结果。现在,让我们在 示例5-13 里出现过的 impl 块中添加 can_hold 方法,如 示例5-15 所示。

/* 示例5-15:基于Rectangle实现can_hold方法,
该方法可以接收另外一个Rectangle作为参数 */

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

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

        当你将这段代码与 示例5-14 中的 main 函数合并运行后,就可以得到预期的输出结果。实际上,方法同样可以在 self 参数后增加签名来接收多个参数,就如同函数一样。 

 

4. 关联函数

        除了方法,impl 块还允许我们定义不用接收 self 作为参数的函数。由于这类函数与结构体相互关联,所以它们也被称为关联函数(associated function)。

        我们将其命名为函数而不是方法,是因为它们不会作用于某个具体的结构体实例。你曾经接触过的 String::from 就是关联函数的一种。

        关联函数常常被用作构造器来返回一个结构体的新实例。例如,我们可以编写一个接收一个维度参数的关联函数,它会将输入的参数同时用作长度与宽度来构造正方形的 Rectangle 实例:

impl Rectangle {
    fn square(size: u32) -> Rectangle {
        Rectangle { width: size, height: size }
    }
}

        我们可以在类型名称后添加 :: 来调用关联函数,就像 let sq = Rectangle:: square(3); 一样。这个函数位于结构体的命名空间中,这里的 :: 语法不仅被用于关联函数,还被用于模块创建的命名空间。我们后面再讨论此处的模块概念。 

5. 多个impl块

        每个结构体可以拥有多个 impl 块。例如,示例5-15 中的代码等价于 示例5-16 中的,下面的代码将方法放置到了不同的 impl 块中。 

// 示例5-16:使用多个impl块来重写示例5-15

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

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

        虽然这里没有采用多个 impl 块的必要,但它仍然是合法的。我们会在后面文章中讨论泛型和 trait 时看到多个 impl 块的实际应用场景。 

 

01-03 22:53