如何在不包装不安全函数的情况下传递一个不安全函数,当期望一个安全函数时?

4

我希望将pthread_create指向我稍后链接到的C函数,该C函数将使用pthread_cleanup_pushpthread_cleanup_pop,这些都是C宏,因此无法移植到Rust。

以下是我的代码:

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());
}

我原本以为既然我已经在调用libc的FFI,那么我可以直接指向一个外部的C函数,但是我却收到了一个错误:

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}`

我可以编写一个包装器,在unsafe{}块中调用C函数,但有没有避免这种情况的方法?


3
那听起来像是libc的一个bug。您考虑过提交问题报告或请求更改签名吗? - Shepmaster
1
如果您正在使用Rust代码调用C代码,并将传递一个C函数,则可以选择另一种方法:直接从C代码中调用pthread_create,跳过整个繁琐的过程。 - Shepmaster
要求只传递安全代码给C看起来是多余的,但一个简单的解决方法是创建一个 extern "C" fn wrapped_fn1(arg: *mut c_void) -> *mut c_void { unsafe { thr_fn1(arg) } } 并将 wrapped_fn1 传递给 pthread_create。此外,在 main 中需要一个 unsafe 块来调用 libc::pthread_createstd::mem::zeroed - user4815162342
1个回答

1

libc函数定义错误:C头文件/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"生成了这个函数定义:

bindgen


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;

这个编译可以在macOS和Linux上都进行。我不确定在这里使用Option是否是一个好主意;为什么有人会启动一个不调用函数的线程呢?

我为libc打开了一个PR来纠正这个问题(没有Option部分)


我不确定在这里使用Option是否是一个好主意,因为bindgen是一种自动化工具,无法从函数签名中分辨出指针是否应该为NULL - Shepmaster
@Shepmaster,那么您也建议在这里不使用 Option,是吗? - hansaplast
很难说。我不明白为什么你会这样做,但是这样的调用经常会在边缘情况的参数中出现过载行为。例如,kill (2) 允许使用 PID 0 作为参数,尽管没有进程可以具有 PID 0。 - Shepmaster
这个发现似乎与问题中报告的错误无关。如果arg3是一个Option,你应该会看到一个类似“期望Option<x>,但找到了<x>”的错误,而不是“期望正常fn,但找到了不安全的fn”。 - user4815162342
@user4815162342 关键是将 unsafe 添加到函数定义中解决了这个问题,也请参阅PR,在那里我只添加了 unsafe 但省略了 Option - hansaplast
1
@Shepmaster,我翻阅了手册,还查看了我的unix书中的部分内容(unix环境下的高级编程),但无处指定如果将null传递给fn参数(第三个参数)会发生什么。在测试中,在Linux上线程启动时确实会出现段错误,而在OSX上只会在pthread_join时发生“仅”段错误,因此我想可以肯定地说这永远不应该是Null - hansaplast

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接