this post was submitted on 22 Oct 2023
4 points (83.3% liked)

Rust

5999 readers
4 users here now

Welcome to the Rust community! This is a place to discuss about the Rust programming language.

Wormhole

!performance@programming.dev

Credits

  • The icon is a modified version of the official rust logo (changing the colors to a gradient and black background)

founded 1 year ago
MODERATORS
 

I'm slowly starting Rust for Rustaceans, and it's already poking holes in my understanding of Rust. Here's a couple initial questions I have:

A shared reference, &T is , as the name implies, a pointer that may be shared. Any number of references may exist to the same value, and each shared reference is Copy, so you can trivially make more of them

I don't understand why a shared reference has to implement copy. In fact, isn't this not true just by the fact that references work for Strings and Strings size can't be known at compile time?

  1. I'm having trouble with the idea of assigning a new value to a mutable reference.

let mut x = Box::new(42); *x = 84;

Why in this example, is the assignment dereferenced. Why not just do x=84? is it dereferenced specifically because is Boxed on the heap?

all 6 comments
sorted by: hot top controversial new old
[–] snaggen@programming.dev 4 points 1 year ago* (last edited 1 year ago) (1 children)

A reference &T holds a pointer, ie. the memory adress to the actual content of T

So, in the example x doesn't hold the value 42, it holds the memory adress to the memory there the integer value 42 is stored. So, to access the value, you need to dereference the reference. Which is why you need to use *x when you assign the value.

[–] snaggen@programming.dev 2 points 1 year ago (1 children)

And the Copy question.It is not that s reference has to implement Copy. A reference IS Copy, by the simple fact that it is a primitive value on the stack.

[–] KillTheMule@programming.dev 4 points 1 year ago* (last edited 1 year ago)

A reference IS Copy, by the simple fact that it is a primitive value on the stack.

This seems a bit misleading, noting that unique/mutable references aren't Copy. Shared references are Copy because it's sound to have that, and it's a huge QOL improvement over the alternative.

[–] KillTheMule@programming.dev 3 points 1 year ago* (last edited 1 year ago)

In fact, isn’t this not true just by the fact that references work for Strings and Strings size can’t be known at compile time?

I don't understand this. Shared references to String are Copy, too. This doesn't have to do anything with sizes. Rather, it's implemented in the compiler, because it's sound to have it and a huge QoL improvement over the alternative... just the same reason why e.g. usize is Copy, really.

is it dereferenced specifically because is Boxed on the heap?

No, it's not really related to the heap. Box implements DerefMut, which is in-depth explained here.

[–] BatmanAoD@programming.dev 1 points 1 year ago

The key thing to understand is that in Rust, references are considered unique types. This means that &T is a separate type from T.

So, for #1, it is not saying that T implements Copy, it is saying that regardless of what T is, &T implements Copy. This is because, by definition, it is always valid to copy a shared reference, even if T itself is not Copy.

Part of the reason this is confusing is that traits often include references in their function signatures; and in particular, Clone::clone has the signature fn clone(&self) -> Self. So when T implements clone, it has a method that takes &T and returns T. But even though the signature takes &T, the type that implements Clone is T itself. (&T always implements Clone as well, just as it always implements Copy, but as with Copy, this is independent from whether T itself implements Clone. See for example the error message you get when explicitly cloning a shared reference: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=a1b80cc83570321868c4ad55ee3353dc)

Since Copy is a marker trait, it doesn't have any associated methods making it explicit that Copy affects how &T can be used. However, Copy requires the type to implement Clone (even though you can implement Clone in terms of Copy) and implies that T can always be automatically created from &T without an explicit call to T::clone, so you can think of the "signature" for Copy as matching that of Clone, even though there's no actual copy method.

For #2, I recommend thinking in terms of explicit types. Adding annotations, you get:

let mut x: Box = Box::new(42); *x = 84_i32;

The type of x is Box. You cannot assign an i32 to a Box; they're different types! But the type of *x is i32, and thus you can assign 84 to it.

The trait used to make Box behave this way is DerefMut, which explicitly makes *x assignable: https://doc.rust-lang.org/std/ops/trait.DerefMut.html