Equivalent of struct->ptr = (char*) data in rust

There's the following line in C:

my_struct->ptr = (char*) data

where as you can see, my_struct has a ptr member of type char*

I did this on Rust:


struct MyStruct {
    ptr: *mut libc::c_char
}

then

unsafe{*my_struct}.ptr = Box::into_raw(my_data) as *mut libc::c_char

where obviously my_struct: *mut MyStruct and my_data: MyData, an arbitraty type.

Then, to access this pointer, I do

let m: &mut MyData = unsafe{(*my_struct).ptr as *mut MyData}.unwrap()

but I'm getting either an unwrap or a segfault in this line above, I can´t debug cause it's a dinamically loadable C function into a non debugabble C program.

Did I do things right?

Here's the actual MyStruct:

I need to do my_struct->ptr = (char*) data in Rust for this .ptr

Where is your *mut MyStruct coming from? Lifetime of MyStruct also needs to be managed somehow, and it may be a dangling pointer (regardless of its content).

BTW:

#[repr(C)]
struct MyStruct {
   data: Box<MyData>,
}

is binary-compatible with C struct with a char* field, and you can use that type in FFI while telling C that it's char*.

MyStruct is actually mysql's UDF_INIT and it's generated through bindgen so I cannot change it.

unsafe{*my_struct}.ptr = Box::into_raw(my_data) as *mut libc::c_char

then calling, imediatelly,

unsafe{(*my_struct).ptr as *mut MyData}.unwrap() fails

Is something wrong on how I set this value?

I suspect this is the summary of what I'm doing:

#[derive(Clone, Copy)]
struct MyStruct {
    ptr: *mut libc::c_char
}

struct MyData{}

fn main() {
    let my_struct = &mut MyStruct{
        ptr: std::ptr::null_mut()
    } as *mut MyStruct;
    let my_data = Box::new(MyData{});
    unsafe{*my_struct}.ptr = Box::into_raw(my_data) as *mut libc::c_char;
    unsafe{((*my_struct).ptr as *mut MyData).as_mut()}.unwrap();
}

Why do I get a None unwrap?

When you do unsafe{*my_struct}.ptr the unsafe block makes a copy and you assign to the field of that copy.

Then when you access the field of the original MyStruct, it never changed.

You can see it by removing the Copy implementation.

To solve it you can make the unsafe block take the whole line.

unsafe{(*my_struct).ptr = Box::into_raw(my_data) as *mut libc::c_char};

why does unsafe copy?

I think this thread should answer your question When does dereferencing a pointer create a copy?

It's a very weird and dangerous way to make a MyStruct pointer. &mut is a temporary loan of a value, and it has to borrow from somewhere. Normally Rust won't let you borrow from an invalid place, but you're using a raw pointer to bypass this protection.

In your case the location you're borrowing from is a temporary on-stack value, and it becomes invalidated after the end of its temporary scope, so you're getting a dangling pointer to stack space

It's same as in C:

mystruct *my_struct;
mystruct compiler_inserted_temp_for_struct_literal = {NULL};
my_struct = &compiler_inserted_temp_for_struct_literal;
my_struct->ptr = …

Note that Rust doesn't have a GC, and doesn't implicitly allocate anything on the heap, so unlike Java, C# or ObjC, a MyStruct instance isn't any kind of an object with an address. It's a bunch of bytes without a permanent location.

If *mut MyStruct needs to be a usable pointer that the FFI side can rely on having an address and live for longer than temporary scope of a variable, then you may need to create Box<MyStruct> and into_raw it the same way you do for MyData.

If you can use an on-stack MyStruct (i.e. FFI side doesn't keep the pointer, doesn't try to free it), then use this:

let mut my_struct = MyStruct {
   ptr: Box::into_raw(Box::new(MyData{})).cast(),
};
call_ffi(&mut my_struct);

the example was just for reducing my problem to a compilation error. On real life, MySQL passes me a pointer to MyStruct, and MySQL itself is responsible for the life of this pointer, so I can do

unsafe{*my_struct}.ptr = Box::into_raw(my_data) as *mut libc::c_char;

because I've disabled MyData destruction on this scope. When MySQL wants to destroy MyStruct* it tell me so I can do Box::from_raw and thus drop my_data.

Per the Rust Reference, an unsafe { ... } block is just a variation of a regular block expression { ... }. And further up the same page, it notes:

Blocks are always value expressions and evaluate the last operand in value expression context.

The distinction between place expressions and value expressions is important here. They roughly correspond to lvalues and rvalues in C/C++. Since *my_struct is a place expression, unsafe { *my_struct } will copy the value:

When a place expression is evaluated in a value expression context, or is bound by value in a pattern, it denotes the value held in that memory location. If the type of that value implements Copy, then the value will be copied.

Finally, the place expression unsafe { *my_struct }.ptr refers to the ptr field of the unnamed temporary variable that has a copy of my_struct, and assigning to it writes only to the temporary, not to the original. To write to the original my_struct pointer, the full unsafe { (*my_struct).ptr = ...; } is necessary.

4 Likes