Prevent automatically dropping the String's data

I don't understand why we need to call mem::ManuallyDrop::new(s) in the below code.
This code will work by simply declaring s as mut.

use std::mem;

unsafe {
    let s = String::from("hello");

    // Prevent automatically dropping the String's data
    let mut s = mem::ManuallyDrop::new(s);

    let ptr = s.as_mut_ptr();
    let len = s.len();
    let capacity = s.capacity();

    let s = String::from_raw_parts(ptr, len, capacity);

    assert_eq!(String::from("hello"), s);
}

More generally, I don't understand the use of mem::ManuallyDrop.

ManuallyDrop is simply a wrapper that prevents the drop "method" of the thing being wrapped from being called automatically.

Why would we want this?
Because sometimes I get bored and leaking memory is fun (sic).

Also, it is very essential for what you're doing in this example, although you have made it considerably harder to follow by shadowing s. Here is a cleaned up version:

let orig = String::from("hello");
// Let us make sure orig is never dropped
let mut orig = ManuallyDrop::new(orig);

// Now let us re-harvest the data of orig to make final
let final = unsafe {
    String::from_raw_parts(orig.as_mut_ptr(), orig.len(), orig.capacity())
};

So here the drop method of final runs at the end of this code, and the memory for "hello" is freed.
But imagine you didn't make orig as ManuallyDrop. Then first final would go out of scope - freeing the memory for "hello". Then, orig would go out of scope, making the memory for "hello" to be freed again, since its drop was called.
And now you have a double free error! That's why you need a ManuallyDrop in this example.

1 Like

In short:

  • String::from will allocate memory
  • dropping a String will free memory
  • Calling String::from_raw_parts creates a String but doesn't allocate
  • If you remove let mut s = mem::ManuallyDrop::new(s) from your code, then you will have a double-free

See the following example which tracks what happens:

struct MonitoredString(String, String);

impl Drop for MonitoredString {
    fn drop(&mut self) {
        println!("dropped {}", self.1);
    }
}

//use std::mem;

fn main() {
    // allocation for `String` "hello" here
    let mut s = MonitoredString(String::from("hello"), String::from("first `s`"));

    // Prevent automatically dropping the String's data
    //let mut s = mem::ManuallyDrop::new(s);

    let ptr = s.0.as_mut_ptr();
    let len = s.0.len();
    let capacity = s.0.capacity();

    // but there is no allocation here, because `String::from_raw_parts` doesn't allocate
    let s = MonitoredString(unsafe { String::from_raw_parts(ptr, len, capacity) }, String::from("second `s`"));

    println!("comparing");
    assert_eq!(String::from("hello"), s.0);
    println!("compared")
    
    // when this block ends, we deallocate TWICE, though we only have allocated ONCE
}

(Playground)

Output:

comparing
compared
dropped second `s`
dropped first `s`

Errors:

   Compiling playground v0.0.1 (/playground)
    Finished dev [unoptimized + debuginfo] target(s) in 2.36s
     Running `target/debug/playground`
free(): double free detected in tcache 2
timeout: the monitored command dumped core
/playground/tools/entrypoint.sh: line 11:     8 Aborted                 timeout --signal=KILL ${timeout} "$@"

You can see that memory for "hello" is released twice at the end, but looking at the source, we can see it's only allocated once here: String::from("hello").

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.