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-01T14:33:03Z” model: gemini-3-flash-preview provider: google-gemini-cli source_hash: 36001d087e5aeabe658ce2689fe7c286c774fc74045841d62f6cf01108d0980e source_path: ch15-02-deref.md workflow: 16

像普通引用一样处理智能指针 (Treating Smart Pointers Like Regular References)

Treating Smart Pointers Like Regular References

实现 Deref 特征允许你自定义“解引用运算符 (dereference operator)” * (不要与乘法或通配符运算符混淆)的行为。通过以智能指针可以像普通引用一样被处理的方式实现 Deref ,你可以编写操作引用的代码,并将其同样用于智能指针。

Implementing the Deref trait allows you to customize the behavior of the dereference operator * (not to be confused with the multiplication or glob operator). By implementing Deref in such a way that a smart pointer can be treated like a regular reference, you can write code that operates on references and use that code with smart pointers too.

让我们先看看解引用运算符如何与普通引用配合使用。然后,我们将尝试定义一个行为类似于 Box<T> 的自定义类型,并看看为什么解引用运算符在我们的新定义类型上不像引用那样工作。我们将探索实现 Deref 特征如何使智能指针能够以类似于引用的方式工作。接着,我们将研究 Rust 的 deref 强制转换 (deref coercion) 功能,以及它如何让我们能够同时处理引用或智能指针。

Let’s first look at how the dereference operator works with regular references. Then, we’ll try to define a custom type that behaves like Box<T> and see why the dereference operator doesn’t work like a reference on our newly defined type. We’ll explore how implementing the Deref trait makes it possible for smart pointers to work in ways similar to references. Then, we’ll look at Rust’s deref coercion feature and how it lets us work with either references or smart pointers.

通过引用获取值 (Following the Reference to the Value)

Following the Reference to the Value

普通引用是一种指针类型,可以将指针看作是指向存储在别处的值的箭头。在示例 15-6 中,我们创建了一个 i32 值的引用,然后使用解引用运算符跟随引用获取该值。

A regular reference is a type of pointer, and one way to think of a pointer is as an arrow to a value stored somewhere else. In Listing 15-6, we create a reference to an i32 value and then use the dereference operator to follow the reference to the value.

#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-06/src/main.rs}}
}

变量 x 持有一个 i325 。我们将 y 设置为等于 x 的引用。我们可以断言 x 等于 5 。然而,如果我们想对 y 中的值进行断言,我们必须使用 *y 来跟随引用指向的值(因此称为“解引用 (dereference)”),以便编译器可以比较实际的值。一旦我们对 y 进行了解引用,我们就可以访问 y 指向的整数值,并将其与 5 进行比较。

The variable x holds an i32 value 5. We set y equal to a reference to x. We can assert that x is equal to 5. However, if we want to make an assertion about the value in y, we have to use *y to follow the reference to the value it’s pointing to (hence, dereference) so that the compiler can compare the actual value. Once we dereference y, we have access to the integer value y is pointing to that we can compare with 5.

如果我们尝试改为编写 assert_eq!(5, y); ,我们将得到如下编译错误:

If we tried to write assert_eq!(5, y); instead, we would get this compilation error:

{{#include ../listings/ch15-smart-pointers/output-only-01-comparing-to-reference/output.txt}}

不允许将数字与数字引用进行比较,因为它们是不同的类型。我们必须使用解引用运算符来跟随引用指向的值。

Comparing a number and a reference to a number isn’t allowed because they’re different types. We must use the dereference operator to follow the reference to the value it’s pointing to.

像引用一样使用 Box<T> (Using Box<T> Like a Reference)

Using Box<T> Like a Reference

我们可以重写示例 15-6 中的代码,使用 Box<T> 代替引用;在示例 15-7 中对 Box<T> 使用的解引用运算符的功能与示例 15-6 中对引用使用的解引用运算符的功能相同。

We can rewrite the code in Listing 15-6 to use a Box<T> instead of a reference; the dereference operator used on the Box<T> in Listing 15-7 functions in the same way as the dereference operator used on the reference in Listing 15-6.

#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-07/src/main.rs}}
}

示例 15-7 和示例 15-6 之间的主要区别在于,这里我们将 y 设置为指向 x 的拷贝值的 Box 实例,而不是指向 x 值的引用。在最后一个断言中,我们可以使用解引用运算符来跟随 Box 的指针,就像 y 是引用时所做的那样。接下来,我们将通过定义自己的 Box 类型来探索 Box<T> 有什么特殊之处,从而使其能够使用解引用运算符。

The main difference between Listing 15-7 and Listing 15-6 is that here we set y to be an instance of a box pointing to a copied value of x rather than a reference pointing to the value of x. In the last assertion, we can use the dereference operator to follow the box’s pointer in the same way that we did when y was a reference. Next, we’ll explore what is special about Box<T> that enables us to use the dereference operator by defining our own box type.

定义我们自己的智能指针 (Defining Our Own Smart Pointer)

Defining Our Own Smart Pointer

让我们构建一个类似于标准库提供的 Box<T> 类型的包装器类型,以体验智能指针类型在默认情况下与引用的不同表现。然后,我们将看看如何添加使用解引用运算符的能力。

Let’s build a wrapper type similar to the Box<T> type provided by the standard library to experience how smart pointer types behave differently from references by default. Then, we’ll look at how to add the ability to use the dereference operator.

注意:我们将要构建的 MyBox<T> 类型与真实的 Box<T> 之间有一个很大的区别:我们的版本不会将其数据存储在堆上。本例我们将重点放在 Deref 上,因此数据实际存储在哪里并不如类似指针的行为重要。

Note: There’s one big difference between the MyBox<T> type we’re about to build and the real Box<T>: Our version will not store its data on the heap. We are focusing this example on Deref, so where the data is actually stored is less important than the pointer-like behavior.

Box<T> 类型最终被定义为一个带有一个元素的元组结构体,所以示例 15-8 以同样的方式定义了一个 MyBox<T> 类型。我们还将定义一个 new 函数来匹配在 Box<T> 上定义的 new 函数。

The Box<T> type is ultimately defined as a tuple struct with one element, so Listing 15-8 defines a MyBox<T> type in the same way. We’ll also define a new function to match the new function defined on Box<T>.

#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-08/src/main.rs:here}}
}

我们定义了一个名为 MyBox 的结构体并声明了一个泛型参数 T ,因为我们希望我们的类型可以持有任何类型的值。MyBox 类型是一个元组结构体,具有一个 T 类型的元素。MyBox::new 函数接收一个 T 类型的参数并返回一个持有传入值的 MyBox 实例。

We define a struct named MyBox and declare a generic parameter T because we want our type to hold values of any type. The MyBox type is a tuple struct with one element of type T. The MyBox::new function takes one parameter of type T and returns a MyBox instance that holds the value passed in.

让我们尝试将示例 15-7 中的 main 函数添加到示例 15-8 中,并将其更改为使用我们定义的 MyBox<T> 类型而不是 Box<T> 。示例 15-9 中的代码无法通过编译,因为 Rust 不知道如何对 MyBox 进行解引用。

Let’s try adding the main function in Listing 15-7 to Listing 15-8 and changing it to use the MyBox<T> type we’ve defined instead of Box<T>. The code in Listing 15-9 won’t compile, because Rust doesn’t know how to dereference MyBox.

{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-09/src/main.rs:here}}

这是产生的编译错误:

Here’s the resultant compilation error:

{{#include ../listings/ch15-smart-pointers/listing-15-09/output.txt}}

我们的 MyBox<T> 类型不能被解引用,因为我们还没有在我们的类型上实现这种能力。为了启用 * 运算符进行解引用,我们需要实现 Deref 特征。

Our MyBox<T> type can’t be dereferenced because we haven’t implemented that ability on our type. To enable dereferencing with the * operator, we implement the Deref trait.

实现 Deref 特征 (Implementing the Deref Trait)

Implementing the Deref Trait

正如在第 10 章“在类型上实现特征”中所讨论的,要实现一个特征,我们需要为特征要求的某些方法提供实现。由标准库提供的 Deref 特征要求我们实现一个名为 deref 的方法,该方法借用 self 并返回一个内部数据的引用。示例 15-10 包含了要添加到 MyBox<T> 定义中的 Deref 实现。

As discussed in “Implementing a Trait on a Type” in Chapter 10, to implement a trait we need to provide implementations for the trait’s required methods. The Deref trait, provided by the standard library, requires us to implement one method named deref that borrows self and returns a reference to the inner data. Listing 15-10 contains an implementation of Deref to add to the definition of MyBox<T>.

#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-10/src/main.rs:here}}
}

type Target = T; 语法为 Deref 特征定义了一个关联类型。关联类型是声明泛型参数的一种略有不同的方式,但你现在不需要担心它们;我们将在第 20 章更详细地介绍它们。

The type Target = T; syntax defines an associated type for the Deref trait to use. Associated types are a slightly different way of declaring a generic parameter, but you don’t need to worry about them for now; we’ll cover them in more detail in Chapter 20.

我们在 deref 方法体中填充了 &self.0 ,这样 deref 就会返回一个指向我们想用 * 运算符访问的值的引用;回想第 5 章“使用元组结构体创建不同的类型”部分可知, .0 用于访问元组结构体中的第一个值。示例 15-9 中在 MyBox<T> 值上调用 *main 函数现在可以编译了,并且断言通过了!

We fill in the body of the deref method with &self.0 so that deref returns a reference to the value we want to access with the * operator; recall from “Creating Different Types with Tuple Structs” in Chapter 5 that .0 accesses the first value in a tuple struct. The main function in Listing 15-9 that calls * on the MyBox<T> value now compiles, and the assertions pass!

如果没有 Deref 特征,编译器只能对 & 引用进行解引用。 deref 方法赋予了编译器这样一种能力:获取任何实现了 Deref 的类型的值,并调用 deref 方法来获得一个它知道如何解引用的引用。

Without the Deref trait, the compiler can only dereference & references. The deref method gives the compiler the ability to take a value of any type that implements Deref and call the deref method to get a reference that it knows how to dereference.

当我们在示例 15-9 中输入 *y 时,在幕后 Rust 实际上运行了这段代码:

When we entered *y in Listing 15-9, behind the scenes Rust actually ran this code:

*(y.deref())

Rust 将 * 运算符替换为对 deref 方法的调用,然后再进行一次普通解引用,这样我们就不必思考是否需要调用 deref 方法。Rust 的这一特性让我们编写的代码在无论是普通引用还是实现了 Deref 的类型时,其功能都是完全相同的。

Rust substitutes the * operator with a call to the deref method and then a plain dereference so that we don’t have to think about whether or not we need to call the deref method. This Rust feature lets us write code that functions identically whether we have a regular reference or a type that implements Deref.

deref 方法返回的是值的引用,而 *(y.deref()) 括号外的普通解引用仍然是必要的,这与所有权系统有关。如果 deref 方法直接返回该值而不是其引用,那么该值将被移出 self 。在这种情况下,或者在大多数使用解引用运算符的情况下,我们并不想获取 MyBox<T> 内部值的所有权。

The reason the deref method returns a reference to a value, and that the plain dereference outside the parentheses in *(y.deref()) is still necessary, has to do with the ownership system. If the deref method returned the value directly instead of a reference to the value, the value would be moved out of self. We don’t want to take ownership of the inner value inside MyBox in this case or in most cases where we use the dereference operator.

请注意,每当我们代码中使用 * 时, * 运算符会被替换为对 deref 方法的调用,然后再调用一次 * 运算符,仅此一次。因为 * 运算符的这种替换不会无限递归,所以我们最终得到了 i32 类型的数据,这与示例 15-9 中 assert_eq!5 相匹配。

Note that the * operator is replaced with a call to the deref method and then a call to the * operator just once, each time we use a * in our code. Because the substitution of the * operator does not recurse infinitely, we end up with data of type i32, which matches the 5 in assert_eq! in Listing 15-9.

函数和方法中的解引用强制转换 (Using Deref Coercion in Functions and Methods)

Using Deref Coercion in Functions and Methods

“解引用强制转换 (Deref coercion)”可以将实现了 Deref 特征的类型的引用转换为另一种类型的引用。例如,解引用强制转换可以将 &String 转换为 &str ,因为 String 实现了 Deref 特征,使其返回 &str 。解引用强制转换是 Rust 在函数和方法参数上执行的一种便利操作,它仅适用于实现了 Deref 特征的类型。当我们向函数或方法传递特定类型的引用作为参数,而该参数类型与函数或方法定义中的不匹配时,它就会自动发生。一系列对 deref 方法的调用会将我们提供的类型转换为参数所需的类型。

Deref coercion converts a reference to a type that implements the Deref trait into a reference to another type. For example, deref coercion can convert &String to &str because String implements the Deref trait such that it returns &str. Deref coercion is a convenience Rust performs on arguments to functions and methods, and it works only on types that implement the Deref trait. It happens automatically when we pass a reference to a particular type’s value as an argument to a function or method that doesn’t match the parameter type in the function or method definition. A sequence of calls to the deref method converts the type we provided into the type the parameter needs.

解引用强制转换被加入 Rust 是为了让编写函数和方法调用的程序员不需要添加那么多使用 &* 的显式引用和解引用。解引用强制转换功能还让我们能编写更多同时适用于引用或智能指针的代码。

Deref coercion was added to Rust so that programmers writing function and method calls don’t need to add as many explicit references and dereferences with & and *. The deref coercion feature also lets us write more code that can work for either references or smart pointers.

为了看到解引用强制转换的实际应用,让我们使用示例 15-8 中定义的 MyBox<T> 类型,以及示例 15-10 中添加的 Deref 实现。示例 15-11 显示了一个带有字符串切片参数的函数定义。

To see deref coercion in action, let’s use the MyBox<T> type we defined in Listing 15-8 as well as the implementation of Deref that we added in Listing 15-10. Listing 15-11 shows the definition of a function that has a string slice parameter.

#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-11/src/main.rs:here}}
}

我们可以使用字符串切片作为实参来调用 hello 函数,例如 hello("Rust"); 。由于有了解引用强制转换,可以使用 MyBox<String> 类型值的引用来调用 hello ,如示例 15-12 所示。

We can call the hello function with a string slice as an argument, such as hello("Rust");, for example. Deref coercion makes it possible to call hello with a reference to a value of type MyBox<String>, as shown in Listing 15-12.

#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-12/src/main.rs:here}}
}

在这里,我们正在调用 hello 函数,传入实参 &m ,它是对 MyBox<String> 值的引用。因为我们在示例 15-10 中在 MyBox<T> 上实现了 Deref 特征,Rust 可以通过调用 deref&MyBox<String> 转换为 &String 。标准库提供了 String 上的 Deref 实现,该实现返回一个字符串切片,这在 Deref 的 API 文档中可以找到。Rust 再次调用 deref&String 转换为 &str ,这与 hello 函数的定义相匹配。

Here we’re calling the hello function with the argument &m, which is a reference to a MyBox<String> value. Because we implemented the Deref trait on MyBox<T> in Listing 15-10, Rust can turn &MyBox<String> into &String by calling deref. The standard library provides an implementation of Deref on String that returns a string slice, and this is in the API documentation for Deref. Rust calls deref again to turn the &String into &str, which matches the hello function’s definition.

如果 Rust 没有实现解引用强制转换,为了使用 &MyBox<String> 类型的值调用 hello ,我们将不得不编写示例 15-13 中的代码,而不是示例 15-12 中的代码。

If Rust didn’t implement deref coercion, we would have to write the code in Listing 15-13 instead of the code in Listing 15-12 to call hello with a value of type &MyBox<String>.

#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch15-smart-pointers/listing-15-13/src/main.rs:here}}
}

(*m)MyBox<String> 解引用为 String 。然后, &[..] 取得与整个字符串相等的 String 的字符串切片,以匹配 hello 的签名。如果没有解引用强制转换,这段涉及所有这些符号的代码将更难读、写和理解。解引用强制转换允许 Rust 为我们自动处理这些转换。

The (*m) dereferences the MyBox<String> into a String. Then, the & and [..] take a string slice of the String that is equal to the whole string to match the signature of hello. This code without deref coercions is harder to read, write, and understand with all of these symbols involved. Deref coercion allows Rust to handle these conversions for us automatically.

当为所涉及的类型定义了 Deref 特征时,Rust 将分析这些类型并根据需要使用 Deref::deref 任意次,以获得与参数类型匹配的引用。需要插入 Deref::deref 的次数在编译时就已确定,因此利用解引用强制转换不会带来运行时损失!

When the Deref trait is defined for the types involved, Rust will analyze the types and use Deref::deref as many times as necessary to get a reference to match the parameter’s type. The number of times that Deref::deref needs to be inserted is resolved at compile time, so there is no runtime penalty for taking advantage of deref coercion!

解引用强制转换如何与可变性交互 (Handling Deref Coercion with Mutable References)

Handling Deref Coercion with Mutable References

类似于你使用 Deref 特征覆盖不可变引用上的 * 运算符,你可以使用 DerefMut 特征覆盖可变引用上的 * 运算符。

Similar to how you use the Deref trait to override the * operator on immutable references, you can use the DerefMut trait to override the * operator on mutable references.

当 Rust 发现类型和特征实现在以下三种情况下,它会执行解引用强制转换:

Rust does deref coercion when it finds types and trait implementations in three cases:

  1. T: Deref<Target=U> 时,从 &T&U

  2. T: DerefMut<Target=U> 时,从 &mut T&mut U

  3. T: Deref<Target=U> 时,从 &mut T&U

  4. From &T to &U when T: Deref<Target=U>

  5. From &mut T to &mut U when T: DerefMut<Target=U>

  6. From &mut T to &U when T: Deref<Target=U>

前两种情况是相同的,除了第二种实现了可变性。第一种情况声明,如果你有一个 &T ,并且 T 实现了指向某种类型 UDeref ,你可以透明地获得一个 &U 。第二种情况声明,同样地解引用强制转换也发生在可变引用上。

The first two cases are the same except that the second implements mutability. The first case states that if you have a &T, and T implements Deref to some type U, you can get a &U transparently. The second case states that the same deref coercion happens for mutable references.

第三种情况更为微妙:Rust 也会将可变引用强制转换为不可变引用。但反之则“不”可能:不可变引用永远不会强制转换为可变引用。由于借用规则,如果你有一个可变引用,那么该可变引用必须是该数据的唯一引用(否则,程序将无法通过编译)。将一个可变引用转换为一个不可变引用永远不会违反借用规则。将不可变引用转换为可变引用则要求初始的不可变引用是该数据的唯一不可变引用,但借用规则无法保证这一点。因此,Rust 无法假设将不可变引用转换为可变引用是可能的。

The third case is trickier: Rust will also coerce a mutable reference to an immutable one. But the reverse is not possible: Immutable references will never coerce to mutable references. Because of the borrowing rules, if you have a mutable reference, that mutable reference must be the only reference to that data (otherwise, the program wouldn’t compile). Converting one mutable reference to one immutable reference will never break the borrowing rules. Converting an immutable reference to a mutable reference would require that the initial immutable reference is the only immutable reference to that data, but the borrowing rules don’t guarantee that. Therefore, Rust can’t make the assumption that converting an immutable reference to a mutable reference is possible.