包含原始指针的结构可以实现发送和FFI安全吗?

Edd*_*ett 6 ffi rust

我有一个场景,Rust将C调用malloc一个缓冲区并将结果指针存入结构.稍后,struct将被移动到一个线程并传递给一个C函数,该函数会使它变异.

我问题的天真方法看起来像这样(游乐场):

extern crate libc;

use libc::{c_void, malloc, size_t};
use std::thread;

const INITIAL_CAPACITY: size_t = 8;

extern "C" {
    fn mutate(s: *mut Storage);
}

#[repr(C)]
struct Storage {
    #[allow(dead_code)]
    buf: *mut c_void,
    capacity: usize,
}

fn main() {
    let buf = unsafe { malloc(INITIAL_CAPACITY) };
    let mut s = Storage {
        buf: buf,
        capacity: INITIAL_CAPACITY,
    };
    thread::spawn(move || {
        unsafe {
            mutate(&mut s); // mutates s.val, maybe reallocates it, updating s.capacity if so.
        }
    }).join()
        .unwrap();
}
Run Code Online (Sandbox Code Playgroud)

得到:

error[E0277]: the trait bound `*mut libc::c_void: std::marker::Send` is not satisfied in `[closure@src/main.rs:26:19: 30:6 s:Storage]`
  --> src/main.rs:26:5
   |
26 |     thread::spawn(move || {
   |     ^^^^^^^^^^^^^ `*mut libc::c_void` cannot be sent between threads safely
   |
   = help: within `[closure@src/main.rs:26:19: 30:6 s:Storage]`, the trait `std::marker::Send` is not implemented for `*mut libc::c_void`
   = note: required because it appears within the type `Storage`
   = note: required because it appears within the type `[closure@src/main.rs:26:19: 30:6 s:Storage]`
   = note: required by `std::thread::spawn`
Run Code Online (Sandbox Code Playgroud)

这是编译器的说法,因为a *mut c_void没有实现Send,也没有Storage这样做,你不能将它移动到线程闭包中.

我认为使用Unique指针可能会解决这个问题.我们试试吧(游乐场):

#![feature(ptr_internals)]
extern crate libc;

use libc::{c_void, malloc, size_t};
use std::ptr::Unique;
use std::thread;

const INITIAL_CAPACITY: size_t = 8;

extern "C" {
    fn mutate(s: *mut Storage);
}

#[repr(C)]
struct Storage {
    #[allow(dead_code)]
    buf: Unique<c_void>,
    capacity: usize,
}

fn main() {
    let buf = Unique::new(unsafe { malloc(INITIAL_CAPACITY) }).unwrap();
    let mut s = Storage {
        buf: buf,
        capacity: INITIAL_CAPACITY,
    };
    thread::spawn(move || {
        unsafe {
            mutate(&mut s); // mutates s.val, maybe reallocates it, updating s.capacity if so.
        }
    }).join()
        .unwrap();
}
Run Code Online (Sandbox Code Playgroud)

但这给了:

warning: `extern` block uses type `std::ptr::Unique<libc::c_void>` which is not FFI-safe: this struct has unspecified layout
  --> src/main.rs:11:18
   |
11 |     fn mutate(s: *mut Storage);
   |                  ^^^^^^^^^^^^
   |
   = note: #[warn(improper_ctypes)] on by default
   = help: consider adding a #[repr(C)] or #[repr(transparent)] attribute to this struct
Run Code Online (Sandbox Code Playgroud)

有没有办法让Storage结构实现Send并具有可变的指向其实例的FFI安全吗?

Kor*_*nel 6

默认情况下,Rust 假设*mut T在线程之间发送是不安全的,这意味着包含它的结构也不安全。

你可以告诉 Rust 它确实是安全的:

unsafe impl Send for Storage {}
Run Code Online (Sandbox Code Playgroud)

它完全依赖于您对 C 如何使用此指针后面的数据的了解。实现Send意味着在使用此指针后面的对象时,C 不会依赖线程本地存储或线程特定的锁(矛盾的是,这对于大多数“线程不安全”的 C 代码来说都是如此)。

它不需要 C 来同时处理来自多个线程的访问——这就是Sync它的用途。

  • 是的,Rust 不检查原始指针的生命周期。它们拥有 C 指针的所有自由和潜在的崩溃。请参阅 `ptr` 的 `as_ref` 和 `PhantomData` 以在包装器结构周围添加更多类型安全性。这与“发送”/“同步”是分开的。 (2认同)