So, the biggest hurdle I'm experiencing as I am learning how to code in Rust is dealing with ownership/referencing/dereferencing. I think I have a fair handle on how to deal with ownership, but this dereferencing thing I am finding difficult. So, applying the old technique of dealing with the simplest problem possible first and working my way up, here's what I came up with. I'm hoping the people here on this forum can weigh in and correct my misconceptions.
Question #1: In The Book most of the discussion of dereferencing deals with variables that are placed on the heap. What if you are dealing with variables that exist only on the stack? For instance:
let w = 7;
let q = &w;
In this case, am I correct in saying that using either *w or *q really has no meaning?
"Dereferencing" is an operation that applies primarily to references/pointers. Rust allows you to implement dereferencing for other types, but it's generally expected that you only do that for types which "behave like a pointer" in some way.
Looking at your example: w is an integer, not a reference or pointer so we wouldn't really expect dereferencing it to work. And indeed it doesn't
error[E0614]: type `usize` cannot be dereferenced
--> src\bin\main.rs:5:5
|
5 | *w;
| ^^
q however is a &usize, a reference to w. So dereferencing it loads the value of w. What makes you think *q would have "no meaning"?
The first two rows of your stack are correct. Using w gives you 7. Using q gives you the dotted end of the arrow: a reference to the 7, which is a value that points to the 7.*q follows the arrow and gives you 7, just like w. Dereferencing does not change anything. q still exists and can be dereferenced again.
fn one() {
let a = String::new();
// a is moved here, it can no longer be used.
let _b = a;
a; // error[E0382]: use of moved value: `a`
}
fn two() {
let a = String::new();
// reference to a is created. a can still be used, though exactly how it can be used depends on context like how long the reference lives
let b = &a;
// dereferencing can't move out of a, we can read the value but not move it
*b; // error[E0507]: cannot move out of `*b` which is behind a shared reference
}
fn three() {
let a = 0usize;
// reference to a is created. a can still be used, though exactly how it can be used depends on context
let b = &a;
// dereferencing is allowed because the type of a implements Copy, which means its impossible to move out of a. Attempts to move out of a will always result in a new copy of the value
*b; // Creates a new usize value copied from a.
}
String doesn't implement Copy, but usize does. The full compiler errors have some extra detail as well.
The term "dereferencing" does not mean "to destroy a reference". References must always be valid in Rust, i.e. point to a value. Null references are not allowed.
Instead, it is the inverse of referencing. To reference means to create a pointer or reference from a value (in this case, w), and this reference now points to w and is saved in q. Dereferencing (*q) now allows to go from the pointer or reference to its pointee or original value, in this case w.
Even more succinctly:
Referencing: T -> &T, in this case usize -> &usize
Dereferencing: &T -> T, in this case &usize -> usize
Thanks for your answers. That helps. I'm sure I'll have more questions on this topic, but for now I want to keep moving through The Book. Appreciate your help.
I've been waiting to answer, because I haven't had time, and I was hoping someone else would mention it. My favorite answer to the question "what is dereferencing" comes from this answer:
For me this answer was an "aha moment" - *thing gives you a place, meaning:
*p = 5 puts 5 in the place in memory where p is.
let value = *p takes whatever value is in the place in memory where p is and puts it in value
If p is Copy the value will be copied.
If p is not Copy the value will be moved and p will become invalid.