Implicit Deref Coercions with Functions and Methods
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 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.
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.
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.
Filename: src/main.rs
fn hello(name: &str) {
println!("Hello, {name}!");
}
Listing 15-11: A hello
function that has the parameter name
of type &str
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.
Filename: src/main.rs
fn main() {
let m = MyBox::new(String::from("Rust"));
hello(&m);
}
Listing 15-12: Calling hello
with a reference to a MyBox<String>
value, which works because of deref coercion
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.
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>
.
Filename: src/main.rs
fn main() {
let m = MyBox::new(String::from("Rust"));
hello(&(*m)[..]);
}
Listing 15-13: The code we would have to write if Rust didn't have deref coercion
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.
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!