Hi guys, I am new to Rust. I have some questions about the concept of "Dereferencing" using *
I have read some posts and answers on this site, but things still seem unclear to a newbie like me. My questions are as follows:
How * in Rust get the actual value of a reference? While i see the definition of * (deref method from std::ops::Deref) will return a reference to a type T (&T), not the actual value. But when i use *, the result i get is the actual value, not the reference to that actual value.
If my question is not clear, or I have missed anything in this post, I hope everyone will give me feedback so I can change it. Thanks everyone.
*v is equivalent to *Deref::deref(&v). That is, the deref() method isn't the dereference operator itself but part of how it is implemented. The actual dereference of the reference returned is built in to the compiler.
I agree with you on the statement "*v is equivalent to *Deref::deref(&v). That is, the deref() method isn't the dereference operator itself but part of how it is implemented".
The result of the line "Deref::deref(&v)" -> "(&T)". This is where my question comes in. As I understand it, * is a method defined inside std::ops::Deref. And the result of this method is a "Reference to a Value (&T)", not an Actual Value. This is the real question I have, but I have found few satisfactory explanations for this problem (or maybe I don't have enough knowledge to understand what people have explained)
As I said, the dereference of the reference types & and &mut is built in to the compiler. The Deref trait exists to allow using the dereference operator with other smart pointers, and it uses the dereferencing of references as part of how it does that. A smart pointer just needs to explain how the thing it points to can be expressed as a reference and the compiler can then get the value out.
It is not the case that * is always implemented as *Deref::deref(&v). If it was, then all uses of * would be infinite recursion. The Rust Reference states it more precisely:
When applied to a pointer it denotes the pointed-to location.
On non-pointer types *x is equivalent to *std::ops::Deref::deref(&x) in an immutable place expression context and *std::ops::DerefMut::deref_mut(&mut x) in a mutable place expression context.
References are pointers, so they get the built-in primitive dereferencing that refers to memory directly. The job of Deref and DerefMut is to convert all other (non-pointer-type) dereferencing into pointer dereferencing. The reason that there is a Deref implementation for &T is so that generic code that wants <P: Deref> can accept plain references P = &Tas well as library-defined types like P = Rc<T>.
Imagine a world where we had two differently named dereference operators: *<primitive> (which is always the compiler built-in pointer access) and *<call_deref> (which always calls Deref). In that world, we could say that *<call_deref>(v) is implemented as *<primitive>(Deref::deref(&v)).
Do you mean that for non-pointer types, when we use *x, Rust will execute the following command "*std::ops::Deref::deref(&x)".
And for pointer types (Box, String,...), Rust will only call the deref() method that has been implemented for that type, right? So *x for pointer types will be equivalent to "std::ops::Deref::deref(&x)" or not? Does Rust need to add * before "std::ops::Deref::deref(&x)"?
For Pointer types like any &, the operator * is the lowest level we describe. No conversion involving Deref::deref() happens.
This applies to Box as well.
Think of it like a + operation for numbers.
For smart pointers (excepting Box) * converted into Deref::deref() in the way you described.
The key point is that once Deref::deref() returns a reference (&) then the process ends, i.e with *. (Because we got rid of smart pointers in previous dereferences.)
PS: you should take a look at chapter 15 creating your own Box if you havent yet.
Conceptually it's like at some point during compilation, the compiler sees...
SomeContext {
*some_value
}
...and does some translation process internally that's like...
// (pseudo-code)
let expression = match (SomeContext, type_of(some_value)) {
// Built in operation for special types
// (When I talk about "special types" below I'm talking about these
// particular types with built-in primitive * operations)
(_, &_)
| (_, &mut _)
| (_, *const _)
| (_, *mut _)
| (_, Box<_>)
=> *<primitive> some_value,
// Use the traits for all other types
(Shared, _) => *<primitive> <_>::deref(&some_value),
(Exclusive, _) => *<primitive> <_>::deref_mut(&mut some_value),
}
...and depending on the context and the type of some_value, you end up with one of:
After this transformation, all remaining dereference operations are primitive (built-in) dereference operations.
Box<_> is a special type that has a primitive dereference operation to allow you to move out of a Box<_> with a dereference and so on -- "magic abilities" that we (so far) do not have traits for.
String is not a special type, so dereferences of String go through the Deref traits.
Thank you very much, but I may not have enough knowledge to understand what you mentioned above (please don't think that I don't like your answers. I really appreciate your answers. But I know that I may not have enough knowledge)
When you click + in 1 + 2, you see + is implemented as std::ops::Add, which is implemented as + for i32. You (probably) won’t question how Rust compute 1 + 2, the compiler just know. But for more complex type like String::from("1") + "2", the compiler itself does not know how to do it, so it invokes Add::add for that.
It’s pretty much the same for *. The compiler just know how it works for some primitive types (&, &mut, Box). And for complex types, *T invokes Deref::deref.
Others have given some good explanations of this already, but what I mean is that if you have r of type &T or &mut T for some type T, then *r isn't defined in terms of Deref (although & and &mut still implement it for use in generics). The Rust compiler just knows that &T and &mut T represent memory addresses pointing to a value of type T[1], so it can just translate it to an access to whatever is stored at that memory address.