Skip to content

Commit 5be270f

Browse files
committed
Reupload I do understand the * operator in Rust now (updated)
1 parent fc5b67b commit 5be270f

File tree

1 file changed

+149
-0
lines changed

1 file changed

+149
-0
lines changed

content/rust-dereferencing.md

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
+++
2+
title = "I do understand the * operator in Rust now (updated)"
3+
date = 2022-03-19
4+
+++
5+
6+
## Update
7+
8+
[See the original post below.](/#original-post)
9+
10+
I've [linked this post on r/rust](https://www.reddit.com/r/rust/comments/thvc2e/i_still_dont_understand_the_operator_in_rust/?utm_source=share&utm_medium=web2x&context=3) and I've got a ton of helpful answers. I'll explain what I've learned.
11+
12+
Before I had a vague idea that dereferencing means "following the pointer (the reference) and trying to **move** the data from that location". That's why `f3` and `g2` confused me. I thought the reference operator surrounding the dereference somehow cancelled the move:
13+
14+
```rust
15+
// Doesn't compile.
16+
fn g1(thing: &Thing) -> &String {
17+
let tmp = *thing;
18+
// ┗━ Move data out of `thing`.
19+
20+
&tmp.field
21+
}
22+
23+
// Compiles.
24+
fn g2(thing: &Thing) -> &String {
25+
&(*thing).field
26+
// ┃ ┗━ Move data out of `thing`.
27+
// ┗━━━ No actually cancel that.
28+
}
29+
```
30+
31+
But [u/kiujhytg2' comment](https://www.reddit.com/r/rust/comments/thvc2e/comment/i1a8zpw/?utm_source=share&utm_medium=web2x&context=3) made me understand that it was not `*` which forced the move. Turns out there was another trickster hiding in plain sight...
32+
33+
The `=` operator! It had not occured to me before but it all makes sense now.
34+
35+
```rust
36+
// Doesn't compile.
37+
fn g1(thing: &Thing) -> &String {
38+
let tmp = *thing;
39+
// ┃ ┗━ Point directly to the referenced data.
40+
// ┗━━━ Try to copy RHS's value, otherwise move it into `tmp`.
41+
42+
&tmp.field
43+
}
44+
45+
// Compiles.
46+
fn g2(thing: &Thing) -> &String {
47+
&(*thing).field
48+
// ┃ ┗━ Point directly to the referenced data.
49+
// ┗━━━ Create a reference to the expression's value with a matching lifetime.
50+
}
51+
```
52+
53+
I'm still not sure how Rust determines what lifetime the newly created reference should have. It has been pointed out that `*thing` is not exactly just `Thing`. I guess Rust remembers that it's a dereferenced variable and referencing it again restores the original lifetime. Some of the comments pointed me to Rust Reference's [chapter on Place Expressions and Value Expressions](https://doc.rust-lang.org/reference/expressions.html#place-expressions-and-value-expressions). Perhaps it describes it better, I haven't had the time to read it yet.
54+
55+
Thanks to all of the commenters, I appreciate the thorough explanations and a very helpful discussion. If you think I should file a PR somewhere to add this information to Rust's documentation, please let me know in the comments here or on Reddit.
56+
57+
58+
## Original post
59+
60+
I’m trying to explain references to people beginning programming in Rust. Despite using Rust for a couple of years now, I’m unable to answer this question: Why can’t I just deref an immutable reference to access the owned data?
61+
62+
```rust
63+
fn modify(arg: &String) {
64+
let mut arg: String = *arg;
65+
arg.push_str("smth");
66+
}
67+
```
68+
69+
I know how stupid this sounds. But I still genuinely don’t know the reason why not. I don’t think there’s a rule that says "You shall not dereference an immutable reference.". What else could the deref operator do?
70+
71+
I’ve tried to answer that last question, but docs on this topic are rather scarce...
72+
73+
Note: The opposite of referencing by using `&` is *dereferencing*, which is accomplished with the dereference operator, `*`.
74+
75+
[Source](https://doc.rust-lang.org/book/ch04-02-references-and-borrowing.html)
76+
77+
Whatever "opposite of referencing" means in this sentence, it does not mean "taking a reference and returning the value it points to". Despite `&` and `*` being an obvious reference to pointer operators in C, the deref operator is not analogous at all. It’s simply impossible to dereference a reference in Rust (unless a is `Copy`):
78+
79+
```rust
80+
let a = String::from("hello");
81+
let b = &a;
82+
let c = *b; // Error!
83+
```
84+
85+
I think dereferencing acutally isn’t the opposite of referencing in Rust. The opposite of referencing is dropping a reference, and the deref operator is totally unrelated to the ref operator. It could be implemented as a function in std [1]:
86+
87+
```rust
88+
fn copy<T: Copy>(t: &T) -> T { /* copy bytes and return owned value [2] */ }
89+
```
90+
91+
But it gets even worse.
92+
93+
In Rust the meaning of `&` and `*` **depend on the context**. And I’m not talking about left hand side vs right hand side `*a = *b` or pattern matching `let &c = b;`.*Taking a reference* and *dereferencing* both alter their meaning depending on the surrounding code:
94+
95+
Note that each example is supposed to do the same thing — take a reference to `Thing` and return a reference to its field.
96+
97+
```rust
98+
struct Thing {
99+
field: String,
100+
}
101+
// Compiles. Straight and simple.
102+
fn f1(thing: &Thing) -> &String {
103+
&thing.field
104+
}
105+
106+
// Doesn't compile...
107+
fn f2(thing: &Thing) -> &String {
108+
let tmp = thing.field;
109+
110+
&tmp
111+
}
112+
113+
// Compiles?!
114+
fn f3(thing: &Thing) -> &String {
115+
&(thing.field)
116+
}
117+
Its the same with *:
118+
119+
// Doesn't compile...
120+
fn g1(thing: &Thing) -> &String {
121+
let tmp = *thing;
122+
123+
&tmp.field
124+
}
125+
126+
// Compiles?!
127+
fn g2(thing: &Thing) -> &String {
128+
&(*thing).field
129+
}
130+
```
131+
132+
Seeing `f1` compile and `f2` throw an error confused me. But I explained to myself that it’s just the syntax, that the compiler understands `&thing.field` as one integral thing and it can’t be broken down. But there’s no way to explain why `f3` compiles then.
133+
134+
In `f1` and `f3` the reference operator means "re-reference the field with the same lifetime as `Thing`". But in `f2` the same reference operator means "create a new reference with lifetime limited to the current scope".
135+
136+
And in `g1` the deref operator means copy but in `g2` it gets combined with the reference operator, despite the parentheses, and interpreted as "re-reference" again.
137+
138+
Questions to the audience:
139+
140+
What **exactly** does the deref operator do under the hood?
141+
What rule prohibits me from dereferencing an immutable reference?
142+
Why is the meaning of `&` and `*` context-dependent?
143+
Why do the docs suggest that `*` is the opposite of `&` (with their names, by reference to C’s operators, in the note in the book) when they’re clearly not? Why is there no explanation of what `*` does?
144+
145+
---
146+
147+
[1] Except for the left hand side: `*reference = new_value;`. That’s the only place where `*` seems useful.
148+
149+
[2] There would be some issues with smart pointers implementing `Deref`. Maybe they could implement `get_ref` method instead, like [`BufReader` does](https://doc.rust-lang.org/stable/std/io/struct.BufReader.html#method.get_ref).

0 commit comments

Comments
 (0)