pretzelhammer / rust-blog

Educational blog posts for Rust beginners
Apache License 2.0
7.11k stars 380 forks source link

Question about leaking with static refs #3

Closed zmitchell closed 4 years ago

zmitchell commented 4 years ago

I'm trying to understand how leaking works in one of the &'static examples.

It's possible to generate random dynamically allocated data at run-time and return 'static references to it at the cost of leaking memory.

use rand;

// generate random 'static str refs at run-time
fn rand_str_generator() -> &'static str {
    let rand_string = rand::random::<u64>().to_string();
    Box::leak(rand_string.into_boxed_str())
}

Part of my confusion comes from the Box::leak method. The documentation says that it returns a reference to the boxed value. If a reference is returned, that means the data is still there and the program hasn't "forgotten" about it. Maybe I'm misunderstanding what it means to "leak" memory. Can you help me understand?

pretzelhammer commented 4 years ago

Yes, your understanding is correct, for the most part! The key thing to know is that Rust doesn't free memory when a ref type goes out of scope, only when an owned type does.

We take a String convert it into a Box<str> and then call Box::leak on it which returns a &'static mut str without freeing the Box's memory. We now have a ref type that's pointing into memory that isn't owned by anyone so it'll never be freed so it's technically a memory leak. If the ref type goes out of scope Rust will not free its memory. We can kinda salvage this situation using Box::from_raw like so:

use rand;

fn rand_str_generator() -> &'static str {
    let rand_string = rand::random::<u64>().to_string();
    // release ownership of memory without freeing it
    Box::leak(rand_string.into_boxed_str())
}

fn main() {
    let rand_str = rand_str_generator();

    let owned_str;
    unsafe {
        // reclaim ownership of memory
        owned_str = Box::from_raw((rand_str as *const str) as *mut str);
    }

    // now owned_str will be dropped & freed
}

However Box::from_raw is an unsafe function because you can use it to generate multiple owners for the same piece of memory that will result in a double free later:

use rand;

fn rand_str_generator() -> &'static str {
    let rand_string = rand::random::<u64>().to_string();
    // release ownership of memory without freeing it
    Box::leak(rand_string.into_boxed_str())
}

fn main() {
    let rand_str = rand_str_generator();

    unsafe {
        let mut_str_ptr = (rand_str as *const str) as *mut str;
        // uh oh, multiple owners!
        (0..10).map(|_| Box::from_raw(mut_str_ptr)).collect::<Vec<_>>();
    }
}

If you try to run the above program on the Rust playground it'll report:

double free or corruption (fasttop)

Let me know if that answers your question.

pretzelhammer commented 4 years ago

I'm gonna assume your question was answered and close this issue.

zmitchell commented 4 years ago

Yes, sorry. That was very helpful!