Compiler reports an error "create a Rust vector from an unsafe buffer" when creating a Rust vector from an unsafe buffer

Here is the code, copied from docs, it can not pass the test, reports "pointer being freed was not allocated" and "set a breakpoint in malloc_error_break to debug"

#[cfg(test)]
mod tests {
  use std::ptr;
  #[test]
  fn it_should_works() {
    unsafe fn buff<T>(src: *const T, elts: usize) -> Vec<T> {
          let mut buffer = Vec::with_capacity(elts);
          ptr::copy(src, buffer.as_mut_ptr(), elts);
          buffer.set_len(elts);
          buffer
      }

      let arr = Vec::from(["1".to_owned(), "2".to_owned(), "3".to_owned()]);
      let b = unsafe { buff(arr.as_ptr(), 3) };  
  }
}

However, when filling arr with type u32, it works:

#[cfg(test)]
mod tests {
  use std::ptr;
  #[test]
  fn it_works() {
    unsafe fn buff<T>(src: *const T, elts: usize) -> Vec<T> {
          let mut buffer = Vec::with_capacity(elts);
          ptr::copy(src, buffer.as_mut_ptr(), elts);
          buffer.set_len(elts);
          buffer
      }

      let arr = Vec::from([1, 2, 3]);
      let b = unsafe { buff(arr.as_ptr(), 3) };  
  }
}

Or, putting the code into main function also works, here is the playground, but it reports "
free(): double free detected".

My confusions are:
1. what's the differences between the three demo above;
2. how to set a breakpoint in malloc_error_break to debug.

No wonder if you don't hold up its safety invariants, stated in the doc comments upfront:

/// # Safety
///
/// * `ptr` must be correctly aligned for its type and non-zero.
/// * `ptr` must be valid for reads of `elts` contiguous elements of type `T`.
/// HERE >>> * Those elements must not be used after calling this function unless `T: Copy`.

TL;DR: you have a double free. arr.as_ptr() doesn't give away ownership, so you now have two Vecs that try to drop the same elements.

Yeah, that's the exact same thing again. The safety invariant states that you can re-use the source buffer if T: Copy (which String isn't, but u32 is), because in that case, it can't have a destructor, so destroying the original vector only deallocates the buffer, it doesn't incorrectly try to free two sets of copies of the same elements.

1 Like

When you use ptr::copy on a String, you end up with two copies of the string that both own the same string data. Then, once the second copy of the string goes out of scope, it tries to destroy the string data, but it has already been destroyed by the other copy. Hence the error.

I recommend adding a Copy bound to your function so that it can only be used with types that are valid to copy:

unsafe fn buff<T: Copy>(src: *const T, elts: usize) -> Vec<T> {
    let mut buffer = Vec::with_capacity(elts);
    ptr::copy(src, buffer.as_mut_ptr(), elts);
    buffer.set_len(elts);
    buffer
}

@H2CO3 @alice I see, it help me a lot. But one more question, why it can work in fn main but not in unit test, and how to set a breakpoint in malloc_error_break?

It either works in both of them or in neither of them. If you mean that the erroneous code behaves differently across the two setups, then that's Undefined Behavior for you :man_shrugging:

That depends on what debugger you use.

Duplicating a String and destroying it twice will definitely not work in fn main either.

1 Like