Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help


x-i18n: generated_at: “2026-03-01T13:45:55Z” model: gemini-3-flash-preview provider: google-gemini-cli source_hash: 7c99bde44d1cb2735239a441e1898be4cfd4b21ef6862978640491903624d06a source_path: ch05-03-method-syntax.md workflow: 16

方法 (Methods)

Methods

方法与函数类似:我们使用 fn 关键字和名称来声明它们,它们可以有参数和返回值,并且包含一段在从其他地方调用该方法时运行的代码。与函数不同的是,方法是在结构体的上下文中定义的(或者是枚举或特征对象,我们分别在第 6 章第 18 章中介绍),并且它们的第一个参数总是 self,它代表调用该方法的结构体实例。

Methods are similar to functions: We declare them with the fn keyword and a name, they can have parameters and a return value, and they contain some code that’s run when the method is called from somewhere else. Unlike functions, methods are defined within the context of a struct (or an enum or a trait object, which we cover in Chapter 6 and Chapter 18, respectively), and their first parameter is always self, which represents the instance of the struct the method is being called on.

方法语法 (Method Syntax)

Method Syntax

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

Let’s change the area function that has a Rectangle instance as a parameter and instead make an area method defined on the Rectangle struct, as shown in Listing 5-13.

#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/listing-05-13/src/main.rs}}
}

为了在 Rectangle 的上下文中定义函数,我们为 Rectangle 开启了一个 impl(实现)块。这个 impl 块中的所有内容都将与 Rectangle 类型相关联。然后,我们将 area 函数移动到 impl 的花括号内,并将签名中以及函数体各处的第一个(在此例中也是唯一的)参数更改为 self。在 main 中,原先我们调用 area 函数并将 rect1 作为参数传递的地方,现在可以使用“方法语法 (method syntax)”在我们的 Rectangle 实例上调用 area 方法。方法语法位于实例之后:我们添加一个点号,后跟方法名、圆括号和任何参数。

To define the function within the context of Rectangle, we start an impl (implementation) block for Rectangle. Everything within this impl block will be associated with the Rectangle type. Then, we move the area function within the impl curly brackets and change the first (and in this case, only) parameter to be self in the signature and everywhere within the body. In main, where we called the area function and passed rect1 as an argument, we can instead use method syntax to call the area method on our Rectangle instance. The method syntax goes after an instance: We add a dot followed by the method name, parentheses, and any arguments.

area 的签名中,我们使用 &self 而不是 rectangle: &Rectangle&self 实际上是 self: &Self 的缩写。在一个 impl 块中,类型 Selfimpl 块所对应的类型的别名。方法的第一个参数必须有一个名为 self 且类型为 Self 的参数,因此 Rust 允许你在第一个参数位置仅用名称 self 来简写。请注意,我们仍然需要在 self 简写前面使用 & 来表示该方法借用 Self 实例,就像我们在 rectangle: &Rectangle 中所做的那样。方法可以获取 self 的所有权、不可变地借用 self(如我们这里所做的),或者可变地借用 self,就像处理任何其他参数一样。

In the signature for area, we use &self instead of rectangle: &Rectangle. The &self is actually short for self: &Self. Within an impl block, the type Self is an alias for the type that the impl block is for. Methods must have a parameter named self of type Self for their first parameter, so Rust lets you abbreviate this with only the name self in the first parameter spot. Note that we still need to use the & in front of the self shorthand to indicate that this method borrows the Self instance, just as we did in rectangle: &Rectangle. Methods can take ownership of self, borrow self immutably, as we’ve done here, or borrow self mutably, just as they can any other parameter.

我们在这里选择 &self 的原因与在函数版本中使用 &Rectangle 的原因相同:我们不想获取所有权,只想读取结构体中的数据,而不是写入。如果我们想在方法执行过程中更改调用该方法的实例,我们会使用 &mut self 作为第一个参数。通过在第一个参数中使用 self 来获取实例所有权的方法很少见;这种技术通常用于当方法将 self 转换成其他东西,并且你希望防止调用者在转换后使用原始实例时。

We chose &self here for the same reason we used &Rectangle in the function version: We don’t want to take ownership, and we just want to read the data in the struct, not write to it. If we wanted to change the instance that we’ve called the method on as part of what the method does, we’d use &mut self as the first parameter. Having a method that takes ownership of the instance by using just self as the first parameter is rare; this technique is usually used when the method transforms self into something else and you want to prevent the caller from using the original instance after the transformation.

除了提供方法语法和不需要在每个方法的签名中重复 self 的类型之外,使用方法而不是函数的主要原因是为了组织代码。我们将对一个类型的实例可以执行的所有操作都放在一个 impl 块中,而不是让未来的代码使用者在我们提供的库的各个地方寻找 Rectangle 的功能。

The main reason for using methods instead of functions, in addition to providing method syntax and not having to repeat the type of self in every method’s signature, is for organization. We’ve put all the things we can do with an instance of a type in one impl block rather than making future users of our code search for capabilities of Rectangle in various places in the library we provide.

注意,我们可以选择给方法起一个与结构体字段相同的名称。例如,我们可以在 Rectangle 上定义一个也名为 width 的方法:

Note that we can choose to give a method the same name as one of the struct’s fields. For example, we can define a method on Rectangle that is also named width:

#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/no-listing-06-method-field-interaction/src/main.rs:here}}
}

在这里,我们选择让 width 方法在实例的 width 字段值大于 0 时返回 true,如果值为 0 则返回 false:我们可以在同名方法中使用字段来达到任何目的。在 main 中,当我们在 rect1.width 后面跟着圆括号时,Rust 知道我们的意思是 width 方法。当我们不使用圆括号时,Rust 知道我们的意思是 width 字段。

Here, we’re choosing to make the width method return true if the value in the instance’s width field is greater than 0 and false if the value is 0: We can use a field within a method of the same name for any purpose. In main, when we follow rect1.width with parentheses, Rust knows we mean the method width. When we don’t use parentheses, Rust knows we mean the field width.

通常(但并非总是),当我们给方法起一个与字段相同的名称时,我们希望它只返回字段中的值而不做其他操作。像这样的方法被称为 “getter 方法”,Rust 不像其他一些语言那样为结构体字段自动实现它们。getter 方法很有用,因为你可以将字段设为私有但将方法设为公有,从而作为类型公有 API 的一部分实现对该字段的只读访问。我们将在第 7 章中讨论什么是公有和私有,以及如何将字段或方法指定为公有或私有。

Often, but not always, when we give a method the same name as a field we want it to only return the value in the field and do nothing else. Methods like this are called getters, and Rust does not implement them automatically for struct fields as some other languages do. Getters are useful because you can make the field private but the method public and thus enable read-only access to that field as part of the type’s public API. We will discuss what public and private are and how to designate a field or method as public or private in Chapter 7.

-> 运算符在哪? (Where’s the -> Operator?)

Where’s the -> Operator?

在 C 和 C++ 中,调用方法使用两个不同的运算符:如果你直接在对象上调用方法,使用 .;如果你在对象的指针上调用方法,并且需要先对指针进行解引用,则使用 ->。换句话说,如果 object 是一个指针,object->something() 类似于 (*object).something()

In C and C++, two different operators are used for calling methods: You use . if you’re calling a method on the object directly and -> if you’re calling the method on a pointer to the object and need to dereference the pointer first. In other words, if object is a pointer, object->something() is similar to (*object).something().

Rust 没有与 -> 运算符等效的运算符;相反,Rust 有一个名为“自动引用和解引用 (automatic referencing and dereferencing)”的功能。调用方法是 Rust 中少数具有此行为的地方之一。

Rust doesn’t have an equivalent to the -> operator; instead, Rust has a feature called automatic referencing and dereferencing. Calling methods is one of the few places in Rust with this behavior.

它是这样工作的:当你使用 object.something() 调用方法时,Rust 会自动添加 &&mut*,以便 object 符合方法的签名。换句话说,以下代码是相同的:

Here’s how it works: When you call a method with object.something(), Rust automatically adds in &, &mut, or * so that object matches the signature of the method. In other words, the following are the same:

#![allow(unused)]
fn main() {
#[derive(Debug,Copy,Clone)]
struct Point {
    x: f64,
    y: f64,
}

impl Point {
   fn distance(&self, other: &Point) -> f64 {
       let x_squared = f64::powi(other.x - self.x, 2);
       let y_squared = f64::powi(other.y - self.y, 2);

       f64::sqrt(x_squared + y_squared)
   }
}
let p1 = Point { x: 0.0, y: 0.0 };
let p2 = Point { x: 5.0, y: 6.5 };
p1.distance(&p2);
(&p1).distance(&p2);
}

第一种写法看起来整洁得多。这种自动引用的行为之所以有效,是因为方法有一个明确的接收者——self 的类型。给定接收者和方法名称,Rust 可以确定地判断出该方法是读取(&self)、修改(&mut self)还是消耗(self)。Rust 让方法接收者的借用变成隐式的,这在很大程度上让所有权在实践中变得符合人体工程学。

The first one looks much cleaner. This automatic referencing behavior works because methods have a clear receiver—the type of self. Given the receiver and name of a method, Rust can figure out definitively whether the method is reading (&self), mutating (&mut self), or consuming (self). The fact that Rust makes borrowing implicit for method receivers is a big part of making ownership ergonomic in practice.

带有更多参数的方法 (Methods with More Parameters)

Methods with More Parameters

让我们通过在 Rectangle 结构体上实现第二个方法来练习使用方法。这一次,我们希望 Rectangle 的一个实例接收另一个 Rectangle 实例,如果第二个 Rectangle 可以完全放入 self(第一个 Rectangle)内部,则返回 true;否则返回 false。也就是说,一旦我们定义了 can_hold 方法,我们希望能够编写示例 5-14 所示的程序。

Let’s practice using methods by implementing a second method on the Rectangle struct. This time we want an instance of Rectangle to take another instance of Rectangle and return true if the second Rectangle can fit completely within self (the first Rectangle); otherwise, it should return false. That is, once we’ve defined the can_hold method, we want to be able to write the program shown in Listing 5-14.

{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/listing-05-14/src/main.rs}}

预期的输出如下所示,因为 rect2 的两个维度都小于 rect1 的维度,但 rect3rect1 宽:

The expected output would look like the following because both dimensions of rect2 are smaller than the dimensions of rect1, but rect3 is wider than 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 保留 rect2 的所有权,以便在调用 can_hold 方法后可以再次使用它。can_hold 的返回值将是一个布尔值,其实现将分别检查 self 的宽度和高度是否大于另一个 Rectangle 的宽度和高度。让我们将新的 can_hold 方法添加到示例 5-13 的 impl 块中,如示例 5-15 所示。

We know we want to define a method, so it will be within the impl Rectangle block. The method name will be can_hold, and it will take an immutable borrow of another Rectangle as a parameter. We can tell what the type of the parameter will be by looking at the code that calls the method: rect1.can_hold(&rect2) passes in &rect2, which is an immutable borrow to rect2, an instance of Rectangle. This makes sense because we only need to read rect2 (rather than write, which would mean we’d need a mutable borrow), and we want main to retain ownership of rect2 so that we can use it again after calling the can_hold method. The return value of can_hold will be a Boolean, and the implementation will check whether the width and height of self are greater than the width and height of the other Rectangle, respectively. Let’s add the new can_hold method to the impl block from Listing 5-13, shown in Listing 5-15.

#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/listing-05-15/src/main.rs:here}}
}

当我们使用示例 5-14 中的 main 函数运行这段代码时,我们将得到想要的输出。方法可以接收多个参数,我们将这些参数在 self 参数之后添加到签名中,这些参数的作用与函数中的参数完全一样。

When we run this code with the main function in Listing 5-14, we’ll get our desired output. Methods can take multiple parameters that we add to the signature after the self parameter, and those parameters work just like parameters in functions.

关联函数 (Associated Functions)

Associated Functions

impl 块中定义的所有函数都被称为“关联函数 (associated functions)”,因为它们与 impl 后面命名的类型相关联。我们可以定义不以 self 作为第一个参数的关联函数(因此它们不是方法),因为它们不需要该类型的实例来配合。我们已经使用过一个这样的函数:定义在 String 类型上的 String::from 函数。

All functions defined within an impl block are called associated functions because they’re associated with the type named after the impl. We can define associated functions that don’t have self as their first parameter (and thus are not methods) because they don’t need an instance of the type to work with. We’ve already used one function like this: the String::from function that’s defined on the String type.

非方法的关联函数通常用于返回结构体新实例的构造函数。这些函数通常被命名为 new,但 new 并不是一个特殊的名称,也不是语言内置的。例如,我们可以选择提供一个名为 square 的关联函数,它接收一个维度参数,并将其同时用作宽度和高度,从而更容易创建一个正方形的 Rectangle,而不需要指定两次相同的值:

Associated functions that aren’t methods are often used for constructors that will return a new instance of the struct. These are often called new, but new isn’t a special name and isn’t built into the language. For example, we could choose to provide an associated function named square that would have one dimension parameter and use that as both width and height, thus making it easier to create a square Rectangle rather than having to specify the same value twice:

文件名: src/main.rs

#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/no-listing-03-associated-functions/src/main.rs:here}}
}

在返回值类型和函数体中的 Self 关键字是 impl 关键字之后出现的类型的别名,在此例中是 Rectangle

The Self keywords in the return type and in the body of the function are aliases for the type that appears after the impl keyword, which in this case is Rectangle.

要调用这个关联函数,我们使用带有结构体名称的 :: 语法;let sq = Rectangle::square(3); 就是一个例子。该函数由结构体命名空间化::: 语法既用于关联函数,也用于由模块创建的命名空间。我们将在第 7 章讨论模块。

To call this associated function, we use the :: syntax with the struct name; let sq = Rectangle::square(3); is an example. This function is namespaced by the struct: The :: syntax is used for both associated functions and namespaces created by modules. We’ll discuss modules in Chapter 7.

多个 impl 块 (Multiple impl Blocks)

Multiple impl Blocks

每个结构体允许有多个 impl 块。例如,示例 5-15 与示例 5-16 所示的代码是等价的,后者将每个方法放在各自的 impl 块中。

Each struct is allowed to have multiple impl blocks. For example, Listing 5-15 is equivalent to the code shown in Listing 5-16, which has each method in its own impl block.

#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/listing-05-16/src/main.rs:here}}
}

在这里没有理由将这些方法分成多个 impl 块,但这是有效的语法。我们将在第 10 章讨论泛型和特征时看到多个 impl 块有用的情况。

There’s no reason to separate these methods into multiple impl blocks here, but this is valid syntax. We’ll see a case in which multiple impl blocks are useful in Chapter 10, where we discuss generic types and traits.

总结 (Summary)

Summary

结构体允许你创建对你的领域有意义的自定义类型。通过使用结构体,你可以保持相关数据片段的连接,并为每一片段命名,使你的代码清晰。在 impl 块中,你可以定义与你的类型相关联的函数,而方法是一种特殊的关联函数,允许你指定结构体实例所具有的行为。

Structs let you create custom types that are meaningful for your domain. By using structs, you can keep associated pieces of data connected to each other and name each piece to make your code clear. In impl blocks, you can define functions that are associated with your type, and methods are a kind of associated function that let you specify the behavior that instances of your structs have.

但结构体并不是你创建自定义类型的唯一方式:让我们转到 Rust 的枚举功能,为你的工具箱添加另一个工具。

But structs aren’t the only way you can create custom types: Let’s turn to Rust’s enum feature to add another tool to your toolbox.