Может ли структура, содержащая необработанный указатель, реализовать Send и быть безопасной для FFI? - PullRequest
0 голосов
/ 09 мая 2018

У меня есть сценарий, в котором Rust вызовет C для malloc буфера и сохранит полученный указатель в структуру. Позднее структура будет перемещена в поток и передана функции 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();
}

Дает:

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`

Это способ, которым компилятор может сказать, что, поскольку *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();
}

Но это дает:

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

Есть ли способ, чтобы структура Storage реализовала Send и имела изменяемые указатели на свои экземпляры, чтобы быть FFI-безопасной?

1 Ответ

0 голосов
/ 15 мая 2018

По умолчанию Rust предполагает, что *mut T небезопасно отправлять между потоками, и это означает, что структуры, содержащие его, также не безопасны.

Вы можете сказать Rust, что это действительно безопасно:

unsafe impl Send for Storage {}

Он полностью зависит от ваших знаний о том, как C использует данные за этим указателем. Реализация Send означает, что C не будет полагаться на локальное хранилище потока или специфичные для потока блокировки при использовании объекта за этим указателем (как это ни парадоксально, но это верно для большинства «небезопасного» кода C).

Не требуется, чтобы C обрабатывал доступ из нескольких потоков одновременно - для этого Sync.

...