Rust Learning: Deref
tldr
deref()returns reference because- Rust desugars
*ato*a.deref() - Rust needs the size at compile time
- We don’t want to consume by calling
deref()
- Rust desugars
- deref coercion can happen
- on function parameters
- on method call expressions
- on assignments with type annotations
as_ref()andas_deref()always return references (or containers of references), which always implementCopy
Overview
I was practicing rust by doing this and this.
While chatting with Claude and trying to learn, I saw that there is this as_deref() call in the iterator chain.
This triggered some memory (trauma) about Deref and I decided to give it another shot with the help of Claude.
The following is (as usual), what I learned from teacher Claude.
The Confusion
When I first saw Deref trait, my instinct/past experience is telling me this is an “interface” that you could implement on your custom type so when you do *my_instance it triggers the specific “dereference” logic and gives you the underlying value.
Then if we look at Deref’s definition here, you can tell the required method is returning a reference instead of a concrete.
So to summarize my confusion: Why is deref() in Deref trait returning a reference when it’s “dereferencing”?
The Answer
Apparently in Rust, when you call * on a type that implemented the Deref trait it would call deref() then still slap on the * operator
But Why?
The above seems to make sense but why can’t the deref() function return Target instead of &Target?
Claude explained that it’s because
Targetmight not beSized, so its size might not be known at compile time. In Rust, types must beSizedto be passed or returned by value because the compiler needs to know how much stack space to allocate. For types likestror[T], they must live behind a pointer (like&str).- Returning
Targetwould move it out from the containing struct, causing issues.
Deref Coercion
Now since we are reading/learning about Deref, the term Deref Coercion would be shown and we would be confused.
Deref Coercion in function parameters
Let’s look at an example below.
String implements Deref and its Target is str, its deref() returns &str.
yo() takes &str but passed &String, Rust would do a implicit conversion (coercion) from &String to &str.
Note that Rust would do this as many times as needed. So if we have a Box that has a String in it, this would work the same.
Deref Coercion in method call expressions
Deref Coercion not only happens when passing function arguments, but also happens when we call methods.
The method call coercion doesn’t require you to add & yourself because in Method-call expressions the derefs are being considered.
The first step is to build a list of candidate receiver types. Obtain these by repeatedly dereferencing the receiver expression’s type, adding each type encountered to the list, then finally attempting an array unsized coercion at the end, and adding the result type if that is successful.
Deref Coercion in assignments with type annotations
Deref Coercion could also happen when doing an assignment with type annotations. This is kinda self explanatory so I’ll just skip.
as_deref() on Option
If you still remember, the reason I started this post is I saw this as_deref call in the iterator chain and got me thinking.
But all the discussions above don’t really help, do they?
Although the place where I was confused is on Vec, later practice shows that it might be more beneficial to understand as_deref on Options.
What does as_deref() on Option do?
Let’s look at as_ref() first
To speak about as_deref() on Options let’s have a look at as_ref() first.
Option’s as_ref says it
Converts from &Option
to Option<&T>.
So given &Option<String> I can get an Option<&String>.
Yeah yeah sure sure, but what exactly does this mean?
Think about the following, if I have an Option<String>, and I have another function that takes in a &String.
Is there a way for me to pass the String in the Option as a borrow to the function without consuming the original Option?
as_ref() is the answer, because it converts &Option<String> to Option<&String>, this gives you the ability to look inside the option without consuming it.
Let’s have a look at an example
|
|
Now deref() should feel natural
What if the function above requires &str instead of &String?
here is where as_deref() comes into play, it not only does what as_ref() does, it also calls deref() as it sees fit to do the conversions.
Granted the as_deref() is not strictly needed in this specific case because &String would be coerced to &str anyways.
Then the natural question is, what is the case where I absolutely needs as_deref() over as_ref()?
The answer is when yo() requires Option<&str>
|
|
But How are these implemented?
Now we know why does as_ref() and as_deref() exist, when and why to use them.
The next question is, how are they implemented?
How are as_ref() implemented?
Let’s have a look at Option’s as_ref()
value.as_ref()is being calledas_ref(&self)takes&self, which is a short hand ofas_ref(self: &Self)- Which means
SelfisOption<String> - Which means
self’s type is&Option<String>
Now we are clear about the types of self and Self.
Let’s go to the match
- We are matching
*self(note that this could be written better with matching ergonomics) - As we said,
self’s type is&Option<String>so matching*selfmeans matchingOption<String> - Now we are looking at
Some(ref x)matching onOption<String>ref xin a pattern means “instead of moving the matched value, give me a reference to it”.- So we got a reference of
Stringwhich is&String - And we returned it as
Some(x)which is of typeOption<&String>
How are as_deref() implemented?
Let’s see the code first.
And let’s use our example
value.as_deref()being calledas_deref(&self)takes&self, which meansself: &Selfwhich meansself’s type is&Option<String>self.as_ref()is called, we getOption<&String>back- We call
mapwithDeref::deref, soOption<&String>becomesOption<&str>- This is because
String’sDerefTarget isstr - and
deref()returns&Targetsoderef()returns&str
- This is because
Bonus: DerefMut
DerefMut is the mut version of deref.
If we have a &mut String, Rust can coerce it to &mut str if DerefMut is implemented.