Is a pointer copied or moved?

This works and hence it seems to suggest that pointer is copied not moved. Pointer is small in size and hence cheap to copy.

fn main() {

    let y:&str = "Hello";
    
    foo(y);
    
    foo(y);
}

fn foo(x:&str) {
    println!("{}",x);
}

From what i learn, String is also a pointer which points to a memory slot on the heap. Then why is String moved and not copied? It should be quite cheap to simply copy the reference pointer and not clone the whole heap memory. Am i misunderstanding something here...

//compile error
fn main() {

    let y:String = String::from("Hello");
    
    foo(y);
    
    foo(y);
}

fn foo(x:String) {
    println!("{}",x);
}

String owns the heap allocation. If you were to allow copying it, who's responsible for deallocating?

The issue isn't so much about representation/layout, but rather ownership. A string slice (i.e. &str) doesn't own the backing memory, it's just a read-only view. Copying that around is fine.

Note also that mutable references are not copyable - they're moved (or reborrowed). But this is back to ownership and aliasing guarantees, rather than layout.

5 Likes

String is not just a pointer. It is a struct with a pointer, a size and a capacity. And the pointer points to an heap allocated UTF-8 string.

When you clone a String, the struct is copied, a new allocation is performed and the content of the old string is copied on the new heap space.

As you can see, it is totally not a cheap operation, and this is the reason String is Clone but not Copy.

Btw, if you just want to pass the address of a String, then you can use a reference. This change will make your second example work:

fn foo(x: &String) {
    println!("{}",x);
}

As vitalyd said, this is about ownership rather than copying.

1 Like

oh man i think i am getting confused. How about a pointer to a literal? Is it also a struct pointing to an address and is copied since the value it's pointing to is Copy

fn main() {

    let x:i32 = 5;
    
    let z:&i32 = &x;

    println!("{}", z)
}

While references are technically pointers, I have found it helpful to think of references as a separate thing in terms of ownership and borrowing rather than a C-like pointer.

The section on references and borrowing might help explain what's going on here.

In this last example you've given, z is a reference to x. The value of x only appears a single time in memory. If you add another variable a and assign it z (play), there is still only one variable x, but the reference z is copied into a.

Immutable references implement both Copy and Clone traits, but only the reference itself is copied or cloned, not the underlying data. You can see this if the playground is modified to assign a new value to x while the references z and a still exist:

error[E0506]: cannot assign to `x` because it is borrowed
 --> src/main.rs:9:5
  |
4 |     let z: &i32 = &x;
  |                    - borrow of `x` occurs here
...
9 |     x = 7;
  |     ^^^^^ assignment to borrowed `x` occurs here

Rust will not allow assigning a new value here because the outstanding references z and a may depend on it not being modified.

5 Likes

I would agree that it's best not to think of it as a physical thing being moved. It's the equivalent of saying "you're in charge of this thing now". For example:

fn foo(s: &String) {
    // foo only borrows the string's value...

    println!("Let me borrow this for awhile: {}", s);

    // ...so it can't destroy it.
}

fn baz(s: String) {
     // baz is in full charge of the string's value now,
     // just as if we'd defined it here...

     println!("This string is mine: {}", s);

     // ...so the string is dropped once the function ends.
}

fn main() {
    let y = String::from("Hello");

    foo(&y);
    // we can let foo borrow y as many times as we like.
    foo(&y);
   
    // We can also transfer ownership of y to baz...
    baz(y);
    // But then we can't use it again.
    baz(y); // panics
}

The difference with i32 values is that they're trivially copyable. So an i32 version of the baz function takes ownership of a new copy instead of the original value. Strings are expensive to copy so it grabs the original value instead.

But spwitt's link to the docs can probably explain it better than I can.

4 Likes

As @spwitt says, Rust references ARE NOT pointers. The sooner you unlearn this C-ism, the easier it will be for you! (I'm speaking from experience here, took me a while to unlearn too! :slight_smile: )

What I believe is confusing you is, that after compilation, the compiler "happens" to convert references to pointers, but this is an implementation detail.

The added value of a Rust reference vs. a simple C pointer is the ownership semantic. A &reference has additional restrictions on it that a pointer lacks, so they are not interchangeable.

Ownership isn't present in the compiled assembly any more, because the compiler verifies that everything is OK before going down to that level.
(Compare to "type erasure" in java, where the compiler verifies that you are not putting an new Animal in List<Dog>, but during runtime, it's all just "List")

C pointers don't do any validation, Rust references can only be created under certain circumstances.
C pointers don't affect each other, you can dereference all of them always (and watch the explosions..)
Rust references are like locks on the underlying data; one mutable ref will prevent another mutable ref from being accessed while it exists.

4 Likes

Yup. References are not pointers, the same way that pointers are not integers. There is similarity on technical level, but usage is completely different.

3 Likes

I would go a step further and say that & and &mut should be referred to as borrow whenever possible. (A reason why I don't like as_ref() :slight_smile: )

4 Likes

Since only the reference is copied or cloned i don't see the reason why it should implement both traits. Seems to me a shallow copy is all that is needed, why not just implement the Copy trait?

Its ok i think i got it. This is because Clone is a super trait of Copy

Thank you everyone for digging me out of the rabbit hole.

So i should see &T as a reference. Although its ultimately a pointer its best i refer to it as a reference, a special kind of pointer that is compiler checked.

In that sense, String is a pointer and &String is a reference. It's a pointer to a pointer but let's not get there to avoid confusion. It's a reference to a pointer.

To recapitulate what has been said up-thread, although "it's ultimately" implemented as "a pointer" (your words, augmented), the associated semantics are those of a capability either for shared-read access (&) xor for exclusive-mutate access (&mut). In both cases this capability is borrowed from the current owner of the referenced item, which alone has the ability to transfer ownership, including by dropping the item.

The current owner can read the item while there are outstanding & read borrows, but even the owner cannot access the item while there is an outstanding &mut exclusive-mutate borrow.

5 Likes

Be careful there!
much like references, "Owned" types such as String also have additional Ownership semantics on top of their implementation details!

"Owned" and "pointer" (implied to allocated storage) are not necessarily correlated, for example the SmallVec crate stores Owned information on the stack.

I personally think of Rust variables in the form of a physical book.

  • They have an owner, a title, and information inside.
  • The Owner can choose to open the book (or only parts of it), and hold it out for others to read it (i.e. hand out &-references, possibly to only certain fields of a struct).
  • Those looking at it (holders of &-reference) are forbidden from taking out their pens and writing, museum style (can't upgrade & to &mut).
  • The Owner can also decide to change the contents, by writing in it (let mut). While writing, no-one else is allowed to read (owner is shy :blush: )
  • Write access can also be delegated to a trusted friend by loaning out the book to them. (&mut reference). They promise to give it back, but obviously only one person can have it at a time, so owner temporarily loses access, until the friend is done with it (trusted friend's scope ends).
  • Trusted friends can also loan out the book (or parts of it) to their trusted friends (&mut can make further &mut, including only certain fields), but again they lose access to the loaned-out part until they get it back .
  • Father borrowck, (the compiler part that handles lifetimes) makes sure everyone plays nice, and yells if someone tries to break the rules (compiler errors).

edit: I've forgotten probably the most important rule:

  • Father borrowck ensures that you close the museum (all &-borrows expired), or get the book back from your friend (&mut refs), before you hold a book-burning (`drop).
7 Likes

Nice analogy