I've recognized that when moving a dereferenced Box with *Box::new(_), it doesn't call Deref::deref nor DerefMut::deref_mut; it really moves the value, which means *Box::new(_) has ownership, not a dereference of a reference.
An example:
let a = Box::new(String::from("hello");
let b = *a;
I've learned that Box is an extraordinary struct, so that in the case of data move, it actually dereferences like a reference (without Deref trait).
During the movement, what happens to the memory allocated by Box in the heap? Is it freed? Is it replaced with bunch of zeroes? Does it remains only not having any way of being accessed?
I know that memory allocated by String::from will be freed when b drops. I'm not curious of data str type hello, I'm curious of memory which would has a size of size of String.
How can I explicitly dereference a Box without Deref trait? When I try it, it automatically borrows Box by calling Deref::deref.
let a = Box::new(String::from("hello"));
fn test(i: &String) {}
test(&(*a));
The compiler infers that there's no need for moving *a, so it seems like it dereferences by the trait, not directly.
This case successfully consumes the box and the string:
let a = Box::new(String::from("hello"));
fn test(i: String) {}
test(*a)
I've learned that Box is an extraordinary struct, so that in the case of data move, it actually dereferences like a reference (without Deref trait).
It doesn't, which is rather the point. Box alone has access to an intrinsic "deref move" feature which is not actually formalised or otherwise available.
During the movement, what happens to the memory allocated by Box in the heap? Is it freed?
Yes.
How can I explicitly dereference a Box without Deref trait?
By using *.
test(&(*a));
The parens do nothing useful here, this just reborrows the pointee.
Replacing the parens by braces would force moving the dereferenced value.
Related
I'm writing some GPU code for macOS using the metal crate. In doing so, I allocate a Buffer object by calling:
let buffer = device.new_buffer(num_bytes, MTLResourceOptions::StorageModeShared)
This FFIs to Apple's Metal API, which allocates a region of memory that both the CPU and GPU can access and the Rust wrapper returns a Buffer object. I can then get a pointer to this region of memory by doing:
let data = buffer.contents() as *mut u32
In the colloquial sense, this region of memory is uninitialized. However, is this region of memory "uninitialized" in the Rust sense?
Is this sound?
let num_bytes = num_u32 * std::mem::size_of::<u32>();
let buffer = device.new_buffer(num_bytes, MTLResourceOptions::StorageModeShared);
let data = buffer.contents() as *mut u32;
let as_slice = unsafe { slice::from_raw_parts_mut(data, num_u32) };
for i in as_slice {
*i = 42u32;
}
Here I'm writing u32s to a region of memory returned to me by FFI. From the nomicon:
...The subtle aspect of this is that usually, when we use = to assign to a value that the Rust type checker considers to already be initialized (like x[i]), the old value stored on the left-hand side gets dropped. This would be a disaster. However, in this case, the type of the left-hand side is MaybeUninit<Box>, and dropping that does not do anything! See below for some more discussion of this drop issue.
None of the from_raw_parts rules are violated and u32 doesn't have a drop method.
Nonetheless, is this sound?
Would reading from the region (as u32s) before writing to it be sound (nonsense values aside)? The region of memory is valid and u32 is defined for all bit patterns.
Best practices
Now consider a type T that does have a drop method (and you've done all the bindgen and #[repr(C)] nonsense so that it can go across FFI boundaries).
In this situation, should one:
Initialize the buffer in Rust by scanning the region with pointers and calling .write()?
Do:
let as_slice = unsafe { slice::from_raw_parts_mut(data as *mut MaybeUninit<T>, num_t) };
for i in as_slice {
*i = unsafe { MaybeUninit::new(T::new()).assume_init() };
}
Furthermore, after initializing the region, how does the Rust compiler remember this region is initialized on subsequent calls to .contents() later in the program?
Thought experiment
In some cases, the buffer is the output of a GPU kernel and I want to read the results. All the writes occurred in code outside of Rust's control and when I call .contents(), the pointer at the region of memory contains the correct uint32_t values. This thought experiment should relay my concern with this.
Suppose I call C's malloc, which returns an allocated buffer of uninitialized data. Does reading u32 values from this buffer (pointers are properly aligned and in bounds) as any type should fall squarely into undefined behavior.
However, suppose I instead call calloc, which zeros the buffer before returning it. If you don't like calloc, then suppose I have an FFI function that calls malloc, explicitly writes 0 uint32_t types in C, then returns this buffer to Rust. This buffer is initialized with valid u32 bit patterns.
From Rust's perspective, does malloc return "uninitialized" data while calloc returns initialized data?
If the cases are different, how would the Rust compiler know the difference between the two with respect to soundness?
There are multiple parameters to consider when you have an area of memory:
The size of it is the most obvious.
Its alignment is still somewhat obvious.
Whether or not it's initialized -- and notably, for types like bool whether it's initialized with valid values as not all bit-patterns are valid.
Whether it's concurrently read/written.
Focusing on the trickier aspects, the recommendation is:
If the memory is potentially uninitialized, use MaybeUninit.
If the memory is potentially concurrently read/written, use a synchronization method -- be it a Mutex or AtomicXXX or ....
And that's it. Doing so will always be sound, no need to look for "excuses" or "exceptions".
Hence, in your case:
let num_bytes = num_u32 * std::mem::size_of::<u32>();
assert!(num_bytes <= isize::MAX as usize);
let buffer = device.new_buffer(num_bytes, MTLResourceOptions::StorageModeShared);
let data = buffer.contents() as *mut MaybeUninit<u32>;
// Safety:
// - `data` is valid for reads and writes.
// - `data` points to `num_u32` elements.
// - Access to `data` is exclusive for the duration.
// - `num_u32 * size_of::<u32>() <= isize::MAX`.
let as_slice = unsafe { slice::from_raw_parts_mut(data, num_u32) };
for i in as_slice {
i.write(42); // Yes you can write `*i = MaybeUninit::new(42);` too,
// but why would you?
}
// OR with nightly:
as_slice.write_slice(some_slice_of_u32s);
This is very similar to this post on the users forum mentioned in the comment on your question. (here's some links from that post: 2 3)
The answers there aren't the most organized, but it seems like there's four main issues with uninitialized memory:
Rust assumes it is initialized
Rust assumes the memory is a valid bit pattern for the type
The OS may overwrite it
Security vulnerabilities from reading freed memory
For #1, this seems to me to not be an issue, since if there was another version of the FFI function that returned initialized memory instead of uninitialized memory, it would look identical to rust.
I think most people understand #2, and that's not an issue for u32.
#3 could be a problem, but since this is for a specific OS you may be able to ignore this if MacOS guarantees it does not do this.
#4 may or may not be undefined behavior, but it is highly undesirable. This is why you should treat it as uninitialized even if rust thinks it's a list of valid u32s. You don't want rust to think it's valid. Therefore, you should use MaybeUninit even for u32.
MaybeUninit
It's correct to cast the pointer to a slice of MaybeUninit. Your example isn't written correctly, though. assume_init returns T, and you can't assign that to an element from [MaybeUninit<T>]. Fixed:
let as_slice = unsafe { slice::from_raw_parts_mut(data as *mut MaybeUninit<T>, num_t) };
for i in as_slice {
i.write(T::new());
}
Then, turning that slice of MaybeUninit into a slice of T:
let init_slice = unsafe { &mut *(as_slice as *mut [MaybeUninit<T>] as *mut [T]) };
Another issue is that &mut may not be correct to have at all here since you say it's shared between GPU and CPU. Rust depends on your rust code being the only thing that can access &mut data, so you need to ensure any &mut are gone while the GPU accesses the memory. If you want to interlace rust access and GPU access, you need to synchronize them somehow, and only store *mut while the GPU has access (or reacquire it from FFI).
Notes
The code is mainly taken from Initializing an array element-by-element in the MaybeUninit doc, plus the very useful Alternatives section from transmute. The conversion from &mut [MaybeUninit<T>] to &mut [T] is how slice_assume_init_mut is written as well. You don't need to transmute like in the other examples since it is behind a pointer. Another similar example is in the nomicon: Unchecked Uninitialized Memory. That one accesses the elements by index, but it seems like doing that, using * on each &mut MaybeUninit<T>, and calling write are all valid. I used write since it's shortest and is easy to understand. The nomicon also says that using ptr methods like write is also valid, which should be equivalent to using MaybeUninit::write.
There's some nightly [MaybeUninit] methods that will be helpful in the future, like slice_assume_init_mut
I'm playing with unsafe rust and trying to implement and I've found something I don't understand. I thought for sure I'd have a dangling pointer and that I'd get some kind of runtime error when trying to run this, but I don't.
fn main() {
let s1 = String::from("s1");
let s1_raw_ptr: *const String = &s1;
drop(s1);
unsafe {
let s = &*s1_raw_ptr;
println!("s recovered from raw pointer: {:?}", s);
}
}
This outputs:
s recovered from raw pointer: "s1"
I thought that when a value goes out of scope in Rust that it is immediately cleaned up. How is it that dereferencing a raw pointer to a now-dropped value is working?
When a String is dropped in Rust, ultimately what ends up getting called is Allocator::deallocate on the system allocator. After this, using the data is undefined behaviour, so anything could happen! But in practice what tends to happen if there aren't any funky compiler optimizations is that you just get whatever data is stored in memory there. If there aren't any new allocations at that place, then you just get whatever data was there before.
When a memory allocation is freed, nothing happens to the allocation. Clearing the data by setting it to all zero (or some other value) would be pointless, since any newly allocated memory always needs to be initialized by the user of that memory.
Does the variable s in print_struct refer to data on the heap or on the stack?
struct Structure {
x: f64,
y: u32,
/* Use a box, so that Structure isn't copy */
z: Box<char>,
}
fn main() {
let my_struct_boxed = Box::new(Structure {
x: 2.0,
y: 325,
z: Box::new('b'),
});
let my_struct_unboxed = *my_struct_boxed;
print_struct(my_struct_unboxed);
}
fn print_struct(s: Structure) {
println!("{} {} {}", s.x, s.y, s.z);
}
As I understand it, let my_struct_unboxed = *my_struct_boxed; transfers the ownership away from the box, to my_struct_unboxed, and then to s in the function print_struct.
What happens with the actual data? Initially it is copied from the stack onto the heap by calling Box::new(...), but is the data some how moved or copied back to the stack at some point? If so, how? And when is drop called? When s goes out of scope?
The Structure data in my_struct_boxed exists on the heap and the Structure data in my_struct_unboxed exists on the stack.
Therefore naïvely speaking (no compiler optimizations), a move or copy operation when dereferencing (*) your Box will always involve copying of the data. On the borrow-checker/static-analysis side, since the Copy trait is not implemented for Structure, this represents a transfer of ownership of the data to the my_struct_unboxed variable.
When you call print_struct, another copy would take place that would copy the bits in memory representing your Structure from the local variable to the function's arguments call-stack. Semantically, this again represents a transfer of ownership into the print_struct function.
Finally when print_struct goes out of scope, it drops the Structure which it owns.
Reference: std::marker::Copy
Excerpt
It's important to note that in these two examples, the only difference
is whether you are allowed to access [your variable] after the assignment. Under the
hood, both a copy and a move can result in bits being copied in
memory, although this is sometimes optimized away.
Note the last part "this is sometimes optimized away". This is why the earlier descriptions were simplified to assume no compiler optimizations i.e. naïve. In a lot of cases, the compiler will aggressively optimize and inline the code especially with higher values for the opt-level flag.
If so, how?
Both "copy" and "move" are semantically memcpy (though that may be optimised to something else, or even nothing whatsoever).
And when is drop called? When s goes out of scope?
Yes. When print_struct ends it cleans up its local scope, and drops s.
Since it's my first time learning systems programming, I'm having a hard time wrapping my head around the rules. Now, I got confused about memory leaks. Let's consider an example. Say, Rust is throwing a pointer (to a string) which Python is gonna catch.
In Rust, (I'm just sending the pointer of the CString)
use std::ffi::CString;
pub extern fn do_something() -> *const c_char {
CString::new(some_string).unwrap().as_ptr()
}
In Python, (I'm dereferencing the pointer)
def call_rust():
lib = ctypes.cdll.LoadLibrary(rustLib)
lib.do_something.restype = ctypes.c_void_p
c_pointer = lib.do_something()
some_string = ctypes.c_char_p(c_pointer).value
Now, my question is about freeing the memory. I thought it should be freed in Python, but then ownership pops in. Because, as_ptr seems to take an immutable reference. So, I got confused about whether I should free the memory in Rust or Python (or both?). If it's gonna be Rust, then how should I go about freeing it when the control flow has landed back into Python?
Your Rust function do_something constructs a temporary CString, takes a pointer into it, and then drops the CString. The *const c_char is invalid from the instant you return it. If you're on nightly, you probably want CString#into_ptr instead of CString#as_ptr, as the former consumes the CString without deallocating the memory. On stable, you can mem::forget the CString. Then you can worry about who is supposed to free it.
Freeing from Python will be tricky or impossible, since Rust may use a different allocator. The best approach would be to expose a Rust function that takes a c_char pointer, constructs a CString for that pointer (rather than copying the data into a new allocation), and drops it. Unfortunately the middle part (creating the CString) seems impossible on stable for now: CString::from_ptr is unstable.
A workaround would to pass (a pointer to) the entire CString to Python and provide an accessor function to get the char pointer from it. You simply need to box the CString and transmute the box to a raw pointer. Then you can have another function that transmutes the pointer back to a box and lets it drop.
So I understand the simple answer to how this works is that local stuff happens in the stack and box stuff happens on the heap.
However, what happens when you have more complex behavior?
Specifically, lets talk about data that gets held in FFI for an indeterminate amount of time and then has to be resurrected later from a *mut c_void.
If you 'forget' a pointer, using std::mem::forget, or std::mem::transmute() a pointer to a *const pointer how durable is the result?
If (for example) this is done inside a function and then the function returns, does the stack get cleared and the memory become invalid?
Are 'Box' pointers which are heap allocated generally speaking valid until they get destroyed (eg. using read())?
I've been told on IRC that this is generally speaking the correct approach to take:
unsafe fn fp(v:Box<Foo>) -> *const c_void {
return transmute(foo);
}
However, looking at libcore::raw::Box, Box isn't remotely the same as a *const T; is that really ok?
If you 'forget' a pointer, using std::mem::forget, or std::mem::transmute() a pointer to a *const pointer how durable is the result?
If you cast a Box with transmute via the fp function, the pointer will remain valid as long as you like, since transmute is consuming the value and thus the destructor, which frees the memory, does not run. (At least, it is valid until you transmute it back to Box<...> to let the destructor run and free the memory.)
forget has no return value, it just discards the value without running the destructors.
Note, however, transmuting to *const c_void requires extra care, e.g. the Foo inside the Box<Foo> may contain thread-local data, or references, and thus may not be valid to pass between threads, or live forever. (Meaning the pointer itself lives forever/is usable however you like, but the data it points to may not.)
If you start casting & pointers, you need to be very very careful about lifetimes and not letting them escape from the scope of the data to which they point (e.g. you can't return a pointer to a local variable from a function).
If (for example) this is done inside a function and then the function returns, does the stack get cleared and the memory become invalid?
The stack doesn't get 'cleared' (i.e. it's not explicitly zeroed), but it is invalid to use any pointers into a stack frame that doesn't exist any more.
Are 'Box' pointers which are heap allocated generally speaking valid until they get destroyed (eg. using read())?
You'll need to be more specific, ptr::read cannot be called on a Box directly, and calling ptr::read on a *const c_void certainly won't do anything useful.
However, looking at libcore::raw::Box, Box isn't remotely the same as a *const T; is that really ok?
raw::Box is not at all the representation of the normal Box. raw::Box is the representation of the old # (now Gc) type. Box<T> is literally a wrapper around a *mut T.