6

What I am trying to do

I have built a Rust interface, with which I want to interact via C (or C# but it does not really matter for the sake of the question). Because it does not seem to be possible to make a Rust Struct accessible to C I am trying to build some wrapper functions that I can call and that will create the Struct in Rust, call functions of the struct and eventually free the Struct from memory manually.

In order to do this I thought I would pass the pointer to the Struct instance that I create in the init function back to C (or C# and temporary store it as an IntPtr). Then when I call the other functions I would pass the pointer to Rust again, dereference it and call the appropriate functions on the dereferenced Struct, mutating it in the process.

I know that I will have to use unsafe code to do this and I am fine with that. I should probably also point out, that I don't know a lot about life-time management in Rust and it might very well be, that what I am trying to is impossible, because it is quite easy to produce a loose pointer somewhere. In that case, I would wonder how I would need to adjust my approach, because I think I am not the first person who is trying to mutate some sort of state from C inside Rust.

What I tried first

So first of all I made sure to output the correct library and add my native functions to it. In the Cargo.toml I set the lib type to:

[lib]
crate-type = ["cdylib"]

Then I created some functions to interact with the struct and exposed them like this:

#[no_mangle]
pub extern fn init() -> *mut MyStruct {
    let mut struct_instance = MyStruct::default();
    struct_instance.init();
    let raw_pointer_mut = &mut struct_instance as *mut MyStruct;
    return raw_pointer_mut;
}

#[no_mangle]
pub extern fn add_item(struct_instance_ref: *mut MyStruct) {
    unsafe {
        let struct_instance = &mut *struct_instance_ref;

        struct_instance.add_item();
    }
}

As you can see in the init function I am creating the struct and then I return the (mutable) pointer.

I then take the pointer in the add_item function and use it.

Now I tried to test this implementation, because I had some doubts about the pointer still beeing valid. In another Rust module I loaded the .dll and .lib files (I am on Windows, but that should not matter for the question) and then called the functions accordingly like so:

fn main() {
    unsafe {
        let struct_pointer = init();
        add_item(struct_pointer);
        println!("The pointer adress: {:?}", struct_pointer);
    }
}

#[link(name = "my_library.dll")]
extern {
    fn init() -> *mut u32;
    fn add_item(struct_ref: *mut u32);
}

What happened: I did get some memory adress output and (because I am actually creating a file in the real implementation) I could also see that the functions were executed as planned. However the Struct's fields seem to be not mutated. They were basically all empty, what they should not have been after I called the add_item function (and also not after I called the init function).

What I tried after that

I read a bit on life-time management in Rust and therefore tried to allocate the Struct on the heap by using a Box like so:

#[no_mangle]
pub extern fn init() -> *mut Box<MyStruct> {
    let mut struct_instance = MyStruct::default();
    struct_instance.init();
    let raw_pointer_mut = &mut Box::new(struct_instance) as *mut Box<MyStruct>;
    return raw_pointer_mut;
}

#[no_mangle]
pub extern fn add_box(struct_instance_ref: *mut Box<MyStruct>) {
    unsafe {
        let struct_instance = &mut *struct_instance_ref;

        struct_instance.add_box();
    }
}

unfortunately the result was the same as above.

Additional Information

I figured it might be good to also include how the Struct is made up in principle:

#[derive(Default)]
#[repr(C)]
pub struct MyStruct{
    // Some fields...
}

impl MyStruct{
    /// Initializes a new struct.
    pub fn init(&mut self) {
        self.some_field = whatever;
    }

    /// Adds an item to the struct.
    pub fn add_item(
        &mut self,
        maybe_more_data: of_type // Obviously the call in the external function would need to be adjusted to accomodate for that...
    ){
        some_other_function(self); // Calls another function in Rust, that will take the struct instance as an argument and mutate it.
    }
}
4
  • I don't have a solution but maybe some insight. Rust has a strong notion of ownership. Ask yourself: who owns struct_instance? Initially it's on the stack inside init(), but after it returns, the instance is dropped and an invalid pointer is returned. Allocating it on the heap would be the solution, but not in the way you did: the instance is moved to the heap, the Box wrapper is created on the stack, and subsequently also dropped when the function returns. Commented Feb 14, 2021 at 15:55
  • 2
    I think you can use Box::leak to take the heap-allocated value back out of the box and return it, then use Box::from_raw to destroy it later, but there might be a cleaner way. This sounds like a common problem. Commented Feb 14, 2021 at 16:01
  • Thank for that insight. I do now see why my Box wrapper did not work as intended. I will try to use Box::leak now. I also feel like that is a common problem and that my current solution kind of undermines Rusts ownership and memory-safety guidelines... Commented Feb 14, 2021 at 16:12
  • Of course it does, but that's unavoidable when interacting with unsafe languages. Commented Feb 14, 2021 at 16:14

2 Answers 2

15

Rust has a strong notion of ownership. Ask yourself: who owns the MyStruct instance? It's the struct_instance variable, whose lifetime is the scope of the init() function. So after init() returns, the instance is dropped and an invalid pointer is returned.

Allocating the MyStruct on the heap would be the solution, but not in the way you tried: the instance is moved to the heap, but then the Box wrapper tied to the same problematic lifetime, so it destroys the heap-allocated object.

A solution is to use Box::into_raw to take the heap-allocated value back out of the box before the box is dropped:

#[no_mangle]
pub extern fn init() -> *mut MyStruct {
    let mut struct_instance = MyStruct::default();
    struct_instance.init();
    let box = Box::new(struct_instance);
    Box::into_raw(box)
}

To destroy the value later, use Box::from_raw to create a new Box that owns it, then let that box deallocate its contained value when it goes out of scope:

#[no_mangle]
pub extern fn destroy(struct_instance: *mut MyStruct) {
    unsafe { Box::from_raw(struct_instance); }
}

This seems like a common problem, so there might be a more idiomatic solution. Hopefully someone more experienced will chime in.

Sign up to request clarification or add additional context in comments.

4 Comments

Its actually possible to omit the cast as *mut MyStruct and instead return &'static mut ModelComposer from the init function and then take &mut MyStruct as the argument for destroy. Because Box::from_raw() is unsafe I had to free the memory like so unsafe { Box::from_raw(struct_instance); }. Of course this is effectively exactly the same as passing the raw pointer, but it seems to be at least a bit more "rusty". Especially with the lifetime-annotation. Not sure static is totally correct here though...
There is also Box::into_raw() which does the equivalent of leak() and the cast, but makes the intention a bit clearer.
@frankenapps Using a pointer, not a reference, is definitely the more Rusty approach here! References (unlike pointers) have an associated lifetime that is verified by the compiler. By asserting the 'static lifetime you are actually lying to the compiler and sabotaging its safety checks, which can result in undefined behaviour if you use init() and destroy() inappropriately from Rust code. By returning a pointer, you force callers of init() to use unsafe blocks when dereferencing the pointer, which is a good thing: the caller needs to be aware that they need to be careful.
@Thomas Yes, in retrospect I agree, that using a pointer and not a reference is the way to go.
4

I'm adding a simple answer for anyone who comes across this question but doesn't need to box - &mut struct_instance as *mut _ is the correct syntax to get a mutable pointer to a struct on the stack. This syntax is a bit tricky to find documented anywhere, it's easy to miss the initial mut.

Notably, this does not solve the original poster's issue, as returning a pointer to a local is undefined behavior. However, this is the correct solution for calling something via FFI (for which there don't seem to be any better results on Google).

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.