Why can't raw pointers freely move around?

The back story of this issue is for a non-bug:

Or put a different way.. I have a raw pointer that represents a struct, which somewhere nested down inside, there is another pointer, which points to the root object. I get why in pure rust, this is problematic and how box/pinned/weak etc are there to help.

But in my case, I have C creating the object, so rust will never know that it is self-referential. All it knows, is I have a pointer to raw bytes. When I read in the older rust book (Raw Pointers - The Rust Programming Language), it says about pointers:

are plain-old-data, that is, they don't move ownership, again unlike Box , hence the Rust compiler cannot protect against bugs like use-after-free;

So why would having a *mut pointer in a local block, which is then moved into a struct, EVER be a different pointer to the same memory address (and thus failing equality checks inside of C if I pass the new struct pointer into the C library and it is compared to the original pointer)? why isn't the default behavior not to simply "own" the old pointer?

The default behavior when moving an object is to simply not modify the pointer at all. Of course if you create a pointer and then move what it pointed to, the pointer is invalidated.

I'm not sure exactly what you mean with "owning" the pointer.

Someone shared in Discord that my disconnect may be with not understanding how the heap vs stack works. If a raw pointer only lives on a stack, and then I move it into a Struct (thus, allocating on the heap), the pointer will look different to C depending on where the raw pointer was before I moved it. So if C is holding on to reference of the raw pointer, when it was on the stack, and then within rust i "move" the raw pointer to a struct, and now on the heap, the pointer is indeed in two different memory locations.

Moving a pointer should definitely not change or invalidate it. Are you sure that you are not moving what it is pointing to?

1 Like

it is plausible that behind the scenes, what it is pointing "to" is suffering from the same problem. Here is what I believe to be how my code is working:

  • I create a ptr on the stack, earmarked to be later allocated by C (MaybeUninit)
  • I send that ptr to C, to allocate an object, which Rust knows nothing about (outside the extern mapping)
  • Rust then takes that ptr and "moves" it to a Struct, which behind the scenes, I believe is "copying" the pointer, because you can't move something directly from the stack to the heap.
  • Later on, I send that ptr (from the rust heap) to C, so that it can do work on the object it allocated
    earlier
  • When C looks at the object behind the pointer, it accesses pointers within that object, that was a reference to the pointer when the object was first created.
  • When C does an equality check on the nested pointer, to the one rust gave it off the rust heap, they do not match.

So while both C and Rust, are pointing to the same underlying object, C only knows about the original pointers, whereas rust has now seen two in total (one from when it was originally on the stack, and the one now being used, which lives on the heap).

if that is the behavior, then effectively, my fix of "boxing" the original pointer in step one.. means that when I send the ptr to C (which now lives on the rust heap), everyone will see the same pointer when going in/out of FFI.

so the TL:DR; you can move pointers freely IF they are always in the same location (heap or stack), but you can't mix the two.

I assume your referring to this

let mut engine = MaybeUninit::uninit();
startEngine(engine.as_mut_ptr());
let mut assumed = engine.assume_init();

engine is a structure allocated on the stack. Not a pointer.
A second independent structure assumed is created last with a different location on the stack.

correct, but if I were to take as_mut_ptr() which is a *mut engine, and store it on the struct... the raw pointer that lives in the struct is not the same as the one that existed in the stack (because one is a pointer to heap, the other to the stack?)

and the stack doesn't live forever, so really.. the pointer to what lived in the stack, will eventually die

It sounds like both of your issues stem from Rust moving around values that C requires to be immovable. In C you would handle this by using malloc to directly create a raw memory block the size of your struct that lives in the heap, and then use the provided initialization routine to turn that memory into a proper instance of the struct.

To properly imitate this pattern in Rust, you can use the raw allocation APIs in std::alloc. Here's an example of what that would look like:

use libc::{c_int, c_uint};
use std::alloc::{alloc, Layout};

// An example C-style struct
#[repr(C)]
#[derive(Debug)]
struct ExampleStruct {
    a: c_int,
    b: c_uint,
}
// A C-style struct initializer
#[allow(non_snake_case)]
unsafe extern "C" fn exampleStructInitializer(ptr: *mut ExampleStruct) {
    ptr.write(ExampleStruct { a: 42, b: 1337 });
}

fn main() {
    // Allocate a raw block of memory with the right size and alignment for an
    // `ExampleStruct`, initialize the memory with the initializer function, then
    // convert it into a `Box`, all without moving the heap memory
    let example: Box<ExampleStruct> = unsafe {
        let layout = Layout::new::<ExampleStruct>();
        let raw = alloc(layout) as *mut ExampleStruct;
        exampleStructInitializer(raw);
        Box::from_raw(raw)
    };

    println!("{:#?}", example);
}
1 Like

Putting something inside a struct does not by itself move it to the heap. Structs (as almost all types) are plain values in Rust. You have to use container types in order to request a heap allocation, e.g. Box or Vec.

1 Like

this works though until you need to access to the *mut ExampleStruct again? in my case, the C API (via FFI) only works with the raw pointer, not the "struct" that lives in Rust. Boxing MaybeUninit, allows me to call as_mut_ptr() in the future. I assume there may be another Rust function to convert the boxed struct or another object to wrap it in?

update: oh i see, self.boxed.as_mut() as *mut ExampleStruct

If you only ever use the memory from C then I suppose you could just leave it as a raw pointer and never convert it to a box at all. up to you really.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.