|
| 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 | +It’s 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