Passing pointer to rust function to C FFI

I have a C struct, wrapped in Rust, that takes a user-defined release function. However, I am getting a segfault because the function pointer is moving. I'm not sure what's going on here, and I'm new to unsafe rust and FFI's, any help would be greatly appreciated.

Here's my code so far:

#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct Foo {
    pub release_func: Option<unsafe extern "C" fn(data: gpointer, user_data: gpointer)>,
}

extern "C" fn user_meta_release_func(_data: gpointer, _user_data: gpointer) {
    println!("release func");
}

// somewhere later:
let foo = Foo { release_func: Some(user_meta_release_func)};

When I first set release_func, I see its value is Some(0x0000005555570780) in the debugger. But, when foo's drop trait is called, I then see that the value of release_func is Some(0x3ff0000000000000).
The resulting error is:

Caused by:
  process didn't exit successfully: `/ephemeral_data/monorail/target/debug/deps/deepstream-2ead9f4b11eca70c 'meta::tests::test_adding_user_meta_to_frame' --exact --nocapture` (signal: 11, SIGSEGV: invalid memory reference)
The terminal process "cargo 'test', '--package', 'deepstream', '--lib', '--', 'meta::tests::test_adding_user_meta_to_frame', '--exact', '--nocapture'" terminated with exit code: 101.

I'm not sure what's happening here, and might be attaching the callback incorrectly. I followed FFI - The Rustonomicon as best I could, but didn't see my exact use case as an example.

How are you using foo before it is dropped? Something else must have overwritten the pointer with garbage, since 0x3ff0000000000000 is far outside of the x86 or AMD address spaces.

1 Like

It's the bit pattern for 1.0, whatever that's worth.

6 Likes

I found my mistake, and it was a silly one. In a different part of the function, I was doing:

let foo = unsafe { *mem };
foo.release_func = Some(user_meta_release_func);

But dereferencing a pointer results in a copy. To do this correctly, I needed:

let foo = &mut unsafe { *mem };

That is not correct. Blocks aren't place expressions (they don't preserve "lvalue-ness"), so the address of a block is the address of a temporary. I.e., unsafe { *mem } copies/moves *mem even if you take the address on the outside, see e.g. this minimal repro.

What you should be using instead is

let foo = unsafe { &mut *mem };
2 Likes

Thank you so much, and for the playground example. Do you have any good resources where I can read more about this?

I'm guessing this should be in the language reference and/or the Book.

The issue here is whether &expr refers to an existing memory location vs. a new temporary location. A temporary is created when expr is a value expression (per the Rust Reference), and a value expression is any expression which isn't a place expression (again per the Rust Reference).

1 Like

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.