Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create *mut *mut to a struct

Tags:

rust

ffi

I'm trying to call pthread_join with a pointer to my struct in order that the C thread can fill in the struct to the memory I point it to. (Yes, I'm aware that this is highly unsafe..)

The function signature of pthread_join:

pub unsafe extern fn pthread_join(native: pthread_t,
                                  value: *mut *mut c_void)
                                  -> c_int

I'm doing this as an exercise of porting C code from a book to Rust. The C code:

pthread_t   tid1;
struct foo  *fp;
err = pthread_create(&tid1, NULL, thr_fn1, NULL);
err = pthread_join(tid1, (void *)&fp);

I came up with this code:

extern crate libc;
use libc::{pthread_t, pthread_join};

struct Foo {}

fn main() {
    let tid1:pthread_t = std::mem::uninitialized();
    let mut fp:Box<Foo> = std::mem::uninitialized();
    let value = &mut fp;
    pthread_join(tid1, &mut value);
}

But the error I see is:

error[E0308]: mismatched types
  --> src/bin/11-threads/f04-bogus-pthread-exit.rs:51:24
   |
51 |     pthread_join(tid1, &mut value);
   |                        ^^^^^^^^^^ expected *-ptr, found mutable reference
   |
   = note: expected type `*mut *mut libc::c_void`
              found type `&mut &mut std::boxed::Box<Foo>`

Is it even possible to achieve this just using casts, or do I need to transmute?

like image 401
hansaplast Avatar asked Feb 14 '17 20:02

hansaplast


2 Answers

There are several issues here:

  • Box is a pointer to a heap-allocated resource, you can extract the pointer itself using Box::into_raw(some_box),
  • References are not silently coerced into pointers (even though they have the same representation), you need an explicit cast,
  • You need to cast from your concrete type to c_void, type inference may be able to do that
  • You have a reference to a reference to a pointer, you need a pointer to a pointer; you have one too many levels of indirection.

Let's make it work:

// pthread interface, reduced
struct Void;

fn sample(_: *mut *mut Void) {}

// actual code
struct Foo {}

fn main() {
    let mut p = Box::into_raw(Box::new(Foo{})) as *mut Void;
    sample(&mut p as *mut _);
}

Note that this is leaking memory (as a result of into_raw), normally the memory should be shoved back into a Box with from_raw for the destructor of Foo to be called and the memory to be freed.

like image 82
Matthieu M. Avatar answered Nov 10 '22 16:11

Matthieu M.


The code can't work as written; that is because the C thread doesn't really "fill in the struct" in the memory you point to. It is responsible for allocating its own memory (or receiving it from another thread beforehand) and filling it out. The only thing the C thread "returns" is a single address, and this address is picked up by pthread_join.

This is why pthread_join receives a void **, i.e. the pointer to a void *. That kind of output parameter enables pthread_join to store (return) the void * pointer provided by the freshly finished thread. The thread can provide the pointer either by passing it to pthread_exit or by returning it from the start_routine passed to pthread_create. In Rust, the raw pointer can be received with code like this:

let mut c_result: *mut libc::c_void = ptr::null_mut();
libc::pthread_join(tid1, &mut c_result as *mut _);
// C_RESULT now contains the raw pointer returned by the worker's
// start routine, or passed to pthread_exit()

The contents and size of the memory that the returned pointer points to are a matter of contract between the thread being joined and the thread that is joining it. If the worker thread is implemented in C and designed to be invoked by other C code, then an obvious choice is for it to allocate memory for a result structure, fill it out, and provide a pointer to allocated memory. For example:

struct ThreadResult { ... };

...
ThreadResult *result = malloc(sizeof(struct ThreadResult));
result->field1 = value1;
...
pthread_exit(result);

In that case your Rust code that joins the thread can interpret the result by replicating the C structure and picking up its ownership:

// obtain a raw-pointer c_result through pthread_join as 
// shown above:
let mut c_result = ...;
libc::pthread_join(tid1, &mut c_result as *mut _);

#[repr(C)]
struct ThreadResult { ... } // fields copy-pasted from C

unsafe {
    // convert the raw pointer to a Rust reference, so that we may
    // inspect its contents
    let result = &mut *(c_result as *mut ThreadResult);

    // ... inspect result.field1, etc ...

    // free the memory allocated in the thread
    libc::free(c_result);
    // RESULT is no longer usable
}
like image 5
user4815162342 Avatar answered Nov 10 '22 15:11

user4815162342