4
votes

I want to point pthread_create to a C function I later link to. That C function will use pthread_cleanup_push and pthread_cleanup_pop which are C macros and thus cannot be ported to Rust.

This is my code:

extern crate libc;
use std::ptr::null_mut;
use libc::c_void;

extern "C" {
    fn thr_fn1(arg:*mut c_void) -> *mut c_void;
}

fn main() {
    let mut tid1 = std::mem::zeroed();
    libc::pthread_create(&mut tid1, null_mut(), thr_fn1, null_mut());
}

I expected that since I'm calling libc's FFI anyway, I can just point to an external C function, but I get an error:

error[E0308]: mismatched types
  --> src/bin/11-threads/f05-thread-cleanup.rs:25:49
   |
25 |     libc::pthread_create(&mut tid1, null_mut(), thr_fn1, null_mut());
   |                                                 ^^^^^^^ expected normal fn, found unsafe fn
   |
   = note: expected type `extern "C" fn(*mut libc::c_void) -> *mut libc::c_void`
              found type `unsafe extern "C" fn(*mut libc::c_void) -> *mut libc::c_void {thr_fn1}`

I could write a wrapper which calls the C function in an unsafe{} block, but is there any way to avoid that?

1
That just sounds like a bug with libc. Have you considered filing it or asking them to change the signature? - Shepmaster
If you are using Rust code to call C code to which you are going to pass a C function, that leads to the alternative: just call pthread_create from your C code and skip the whole song and dance. - Shepmaster
Requiring only safe code to pass to C looks superfluous, but a simple workaround would be to create a extern "C" fn wrapped_fn1(arg: *mut c_void) -> *mut c_void { unsafe { thr_fn1(arg) } } and pass wrapped_fn1 to pthread_create. Also, you will need an unsafe block in main to invoke libc::pthread_create and std::mem::zeroed. - user4815162342

1 Answers

1
votes

The libc function definition is wrong: The C header /usr/include/pthread.h:

extern int pthread_create (pthread_t *__restrict __newthread,
                           const pthread_attr_t *__restrict __attr,
                           void *(*__start_routine) (void *),
                           void *__restrict __arg) __THROWNL __nonnull ((1, 3));

bindgen produces this function definition:

pub fn pthread_create(arg1: *mut pthread_t,
                      arg2: *const pthread_attr_t,
                      arg3: Option<unsafe extern "C" fn(arg1: *mut c_void) -> *mut c_void>,
                      arg4: *mut c_void) -> c_int;

Which compiles both on macOS and Linux. I'm not sure Option is a good idea here; why would anyone start a thread which doesn't call a function?

I opened a PR for libc to correct the issue (without the Option part)