How to model a structure with different ownership members?

Hello I have a callback based C interface, and now I want to test it in rust, for example


extern "C" fn read_data( callback: extern "C" fn(*mut std::ffi::c_void, bool),
                         callback_data: *mut std::ffi::c_void){

    struct Sendable{
        callback: extern "C" fn(*mut std::ffi::c_void, bool),
        callback_data: *mut std::ffi::c_void
    };

    unsafe impl Send for Sendable{};

    let sendable = Sendable{
        callback,
        callback_data
    };

    let _detached = std::thread::spawn(move||{
        let read_ok = true;
        (sendable.callback)(sendable.callback_data, read_ok);
    });

}

fn test_read_data(){

    let mut read_result = false;

    let (tx, mut rx) = tokio::sync::oneshot::channel::<bool>();


    let mut callback_data = (&mut read_result as *mut bool,
                         tx);

    extern "C" fn test_callback(callback_data: *mut std::ffi::c_void, result :bool){

        let cast_back = callback_data as *mut (*mut bool, tokio::sync::oneshot::Sender::<bool>);

        unsafe{*(*cast_back).0  = result;}

        unsafe{(*cast_back)}.1.send(true);  // cannot move out of *cast_back which is behind a raw pointer move occurs because ....

       //right here does rust test_callback already takes ownership of dereferenced tuple
       // so I have to make rust 'leak' it (the tuple still owned in read_data scope) to avoid double free?
    }

    read_data(test_callback,
              &mut callback_data as *mut (*mut bool,
                                          tokio::sync::oneshot::Sender::<bool>) as *mut _ );

    let wait = rx.try_recv();

    assert!(unsafe{*callback_data.0});

}

read_data is exported as C interface, so I have to cast pointer instead of other idiomatic rust ways. now I have 3 objects :

  1. the passed in callback_data(behind pointer), I need it lifetime binded to test_read_data, the callback should only using its reference. (does dereference pointer means 'dereferenced value lifetime taken by rust' ? )

  2. the bool field of callback_data, I also need it lifetime binded to test_read_data, the callback should only set it value

  3. the tokio Sender object, I want it 'moved' from test_read_data into 'callback', since the send method need to move Sender object.

I can't find a correct way to satisfy different ownership/lifetime, my question is

  1. what's the idiomatic rust way to do such test ? (read_data signature can't be changed, must be c callback and void* based, and test entry/ test_callback must be also in rust)

  2. ignore the none-idiomatic (or maybe thread bugs) of my code, how to do such thing in rust? I mean, object lifetime still bind to current scope, but passing its pointer out, and at other place(through pointer dereference) , only parts of object member is moved, and then current scope still able to access the none-moved member ? I'm thinking using a Box to wrap the member, but also not success.

any suggestions will be greatly appreciated

I've got this work by using box and option

use tokio;
use std::time;

extern "C" fn read_data( callback: extern "C" fn(*mut std::ffi::c_void, bool),
                         callback_data: *mut std::ffi::c_void){

    struct Sendable{
        callback: extern "C" fn(*mut std::ffi::c_void, bool),
        callback_data: *mut std::ffi::c_void
    };

    unsafe impl Send for Sendable{};

    let sendable = Sendable{
        callback,
        callback_data
    };

    let _detached = std::thread::spawn(move||{
        let read_ok = true;
        (sendable.callback)(sendable.callback_data, read_ok);
    });

}


fn main(){


    let (tx, mut rx) = tokio::sync::oneshot::channel::<bool>();

    let mut callback_data = (false,
                             Some(tx));

    extern "C" fn test_callback(callback_data: *mut std::ffi::c_void, result :bool){

        let mut cast_back = unsafe{Box::from_raw(callback_data as *mut (bool, Option<tokio::sync::oneshot::Sender::<bool>>))};

        cast_back.0 = result;
        let moved_out = cast_back.1.take();

        let _ = moved_out.unwrap().send(true);

        //re-leak it since main function should own it
        Box::into_raw(cast_back);
    }

    read_data(test_callback,
              &mut callback_data as *mut (bool,
                                          Option::<tokio::sync::oneshot::Sender::<bool>>) as *mut _ );

    let mut counter = 0;
    loop {
        match rx.try_recv(){
            Ok(v)=>{
                break;
            }
            Err(tokio::sync::oneshot::error::TryRecvError::Empty)=>{
            }
            Err(tokio::sync::oneshot::error::TryRecvError::Closed)=>{
                assert!(false);
            }
        }

        std::thread::sleep(std::time::Duration::new(1,0));
        counter = counter+1;

        if(counter > 2){
            assert!(false);
        }
    }

    assert!(callback_data.0);

}


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.