Why does Rust make a String variable invalid if it is used in a different function?


#1


https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html

If we tried to use the variable s again, then how come s is no longer valid, why can’t it be still valid?


#2

It’s no longer valid because takes_ownership takes ownership and then doesn’t give it back. This means the string is de-allocated and doesn’t exist after the function returns.

There’s no inherent reasons that it needs to happen that way, it just happens because the code is written like that. If you want the string to be valid after, you can change the function to just borrow it:

fn no_longer_takes_ownership(some_string: &String) {
    // ...
}

However, that wouldn’t be the best way to do it, because it means we need to have a heap-allocated String around, but you can’t do anything with a &String that would require, or benefit from, the string being allocated on the heap.

The idiomatic way would be to just take a string slice:

fn no_longer_takes_ownership(some_string: &str) {
    // ...
}

You should be able to call the function like this:

no_longer_takes_ownership(&s);

#3

Integers can be copied easily and cheaply, so they are.

Strings are more expensive to copy, so they aren’t copied automatically. Instead there is one copy that is moved and can’t be in two places at once.

Integers don’t need to be deallocated individually.

Strings’ storage has to be deallocated exactly once for each string. That’s why the owner is responsible for that - there’s only one owner. So moving ownership means moving and eventually destroying an object.


#4

Integers are stored on the stack, not on the heap cause its size is set. But Strings are stored on the heap as the size can change right? However the String variable has a pointer, length and capacity, and that is stored on the stack while the String data is stored on the heap. So when doing this?

fn main()
{
    let s = String::from("hello")
    takes_ownership(s)
}

fn takes_ownership(some_string:String)
{
    println!("{}", some_string
}

Why can’t it copy the s’s variable contents that are stored on the stack and transfer it to some_string instead of moving it? It is still just as cheap as I am not asking to copy the stuff that is stored on the heap?


#5

Internally, it’s done just like that; the (pointer, length, capacity) triple is copied into the stack frame of takes_ownership. (Optimizations can kick in later and elide the copy.)

The point is that takes_ownership can do what it wants with that String, including appending to it (which can reallocate) or deallocating it. Which means that your original (pointer, length, capacity) triple in main is useless, since the length may be incorrect and the pointer may be dangling. That’s what the compiler prevents by making s inaccessible after ownership transfer.


#6

Oh I see thanks for clearing that up for me :slight_smile:

What do you mean by this though?


#7

The pointer is just a number referencing the location in memory where the string data can be found. When the string storage is reallocated, it will in general change its location in memory. And when it’s deallocated, the original location is not valid anymore either; it may be garbage, or already being reused by another allocated object. This is called a dangling pointer.


#8

Arguably the most salient point here is: who is responsible for freeing (dropping) the String's heap allocation when it’s no longer used? If you were to copy the (ptr, len, cap) triple, then you’d end up with 2 String instances both thinking they own the heap allocation. This shared ownership is relegated to custom types, like Rc (or Arc for threadsafe version).


#9

BTW, to keep s available to main after takes_ownership is finished, you have three options:

  • pass s by shared reference, as suggested by @hannobraun above (then the function can’t change it)
  • pass s by mutable reference as in fn foo(s: &mut String)
  • pass s owned, but return ownership at the end of the function as in fn foo(s: String) -> String