It looks like your original example code block is still just a little off. It should look like this:
fn main() {
let s1 = String::from("hello");
let len = calculate_length(&s1);
println!("The length of '{}' is {}.", s1, len);
}
fn calculate_length(s: &String) -> usize {
s.len()
}
But, I think I understand you question now. You are wondering why the string data is still on the heap after calculate_length()
is done running, right?
Let's go through this step-by-step:
fn main() {
// First, we create the string. This is an "owned string". s1
// "owns" the string data which is on the heap.
let s1 = String::from("hello");
// Now we we pass a "reference" to s1 into "calculate_length"
// by prefixing s1 with an "&". Now lets go look at calculate_lenth
let len = calculate_length(&s1);
// So after our calculate length function is over, the reference
// to s1 that we passed to it was dropped, but s1 is still in scope
// here, so it still owns its data and the string data will
// still exist on the heap.
// To prove that we can still use s1, we print it. We can print s1
// because it is still in scope and still owns its data
println!("The length of '{}' is {}.", s1, len);
} // Now that the main function over, our variable s1 goes out of scope
// and is dropped. Because s1 owns its data, it will clean up its
// data when it is dropped and delete the string data on the heap.
// OK, in this function we say that our argument, s, is a reference to a
// string. This means that it doesn't own the data that it refers to.
// Because s doesn't own its data someone else has to own it. In this case,
// s1 still owns its data.
fn calculate_length(s: &String) -> usize {
// Here we use our reference to the string to read the string data and
// count the length
s.len()
} // Now that our function is over, our local variable s will be dropped,
// but because it doesn't own its data it doesn't delete the string.
// That means that the string is still owned by s1 and still exists.
So, the reason that the string data isn't deleted when s
goes out of scope is because s1
, which owns the data, is still using it. So it can't delete the string data yet. Also, when you create a reference to something, it doesn't use up more heap memory. It just uses up a teeny-tiny bit of space on the stack, which is a pointer. So I could have a string that has a lot of references to it, but even if I had a lot of references, it wouldn't take up any space on the heap, it would just take up a little bit of space on the stack.
// Create a string
let my_string =
String::from("Pretend this is a long string with lots of data");
do_something1(&my_string);
do_something2(&my_string);
do_something3(&my_string);
// And so on...
In the above example, every time I call one of the do_something
functions, I pass a reference to my_string
, but that reference is really tiny and doesn't take up a lot of memory. Each do_something
function will be able to read the entire string from the heap, and even though we give a bunch of different references to my_string
, my_string
is only taking up one spot on the heap.