Закрепленная ссылка на объект FFI, используемый вызывающими методами, которые его получают

#rust #ffi #rust-pin

Вопрос:

Я пишу интерфейс внешних функций (ffi), чтобы предоставить API уже существующей библиотеки C некоторому новому коду Rust, который я пишу. cxx Для этого я использую модуль Rust.

Я сталкиваюсь с некоторыми проблемами, связанными с Pin (темой, которую, должен признать, я не совсем понимаю).

Мой модуль C имеет API, который предоставляет указатели на некоторые содержащиеся объекты из основного объекта, которому принадлежат эти содержащиеся объекты. Вот надуманный пример:

 // test.hpp
#include <string>
#include <memory>

class Row {
    std::string row_data;
public:
    void set_value(const std::stringamp; new_value) {
        this->row_data = new_value;
    }
};

class Table {
    Row row;
public:
    Table() : row() {};
    Row* get_row() {
        return amp;this->row;
    }

};

inline std::unique_ptr<Table> make_table() {
    return std::make_unique<Table>();
}
 

Идея заключается в том, что вы создаете Table объект, который затем позволяет вам получить указатель на него Row , чтобы вы могли им манипулировать.

Моя попытка создать FFI ржавчины выглядит так:

 // main.rs
use std::pin::Pin;

use cxx::let_cxx_string;


#[cxx::bridge]
mod ffi {

    unsafe extern "C  " {
        include!("src/test.hpp");

        type Table;

        pub fn make_table() -> UniquePtr<Table>;

        fn get_row(self: Pin<amp;mut Table>) -> *mut Row;

        type Row;

        pub fn set_value(self: Pin<amp;mut Row>, value: amp;CxxString);
    }
}

impl ffi::Table {
    pub fn get_row_ref<'a>(self: Pin<amp;'a mut ffi::Table>) -> Pin<amp;'a mut ffi::Row> {
        unsafe { Pin::new_unchecked(amp;mut *self.get_row()) }
    }
}

fn main() {

    let mut table = ffi::make_table();

    let row = table.pin_mut().get_row_ref();

    let_cxx_string!(hello="hello world");
    
    row.set_value(amp;hello);
    
    let_cxx_string!(hello2="bye world");
    row.set_value(amp;hello2);


}
 

Note that:

  • cxx Модуль требует, чтобы неконстантные методы C принимали Pin<amp;mut T> в качестве своего приемника
  • Метод C get_row возвращает указатель, который я хочу преобразовать в ссылку на строку, которая имеет тот же срок Table службы, что и объект — владелец get_row_ref -для этого и предназначена оболочка.

У меня есть две проблемы:

  1. Правильно ли это, что я звоню Pin::new_unchecked сюда? Из документации следует, что это не так:

    вызов Pin-кода::new_unchecked для amp;’a mut T небезопасен, потому что, хотя вы можете закрепить его на данный срок службы ‘a, вы не можете контролировать, будет ли он закреплен один раз ‘a заканчивается

    Если это небезопасно, как мне действовать?

  2. Эта программа не может быть скомпилирована со следующей ошибкой:
     error[E0382]: use of moved value: `row`
      --> src/main.rs:41:2
       |
    34 |     let row = table.pin_mut().get_row_ref();
       |         --- move occurs because `row` has type `Pin<amp;mut Row>`, which does not implement the `Copy` trait
    ...
    38 |     row.set_value(amp;hello);
       |     --- value moved here
    ...
    41 |     row.set_value(amp;hello2);
       |     ^^^ value used here after move
    
     

    Первый вызов set_value потребляет закрепленную ссылку, и после этого ее нельзя
    использовать снова. amp;mut T нет Copy , значит Pin<amp;mut Row> Copy , тоже нет.

    Как настроить API таким образом, чтобы ссылка на Row могла использоваться для нескольких последовательных вызовов методов (в пределах ограничений, установленных cxx )?

Для тех, кто хочет попробовать это:

 # Cargo.toml
[dependencies]
cxx = "1.0.52"

[build-dependencies]
cxx-build = "1.0"
 
 // build.rs
fn main() {
    cxx_build::bridge("src/main.rs")
        .flag("-std=c  17")
        .include(".")
        .compile("test");
}
 

Ответ №1:

  1. Правильно ли это, что я звоню Pin::new_unchecked сюда?

Да, это звучит убедительно. В этом контексте мы знаем Row , что это закреплено, потому что:

  1. Тот Table приколот;
  2. Хранится Row встроенным в Table ;
  3. Семантика перемещения C , по сути, означает, что каждый объект C в любом случае «закреплен».
  1. Эта программа не может быть скомпилирована со следующей ошибкой:

Когда вы вызываете метод с обычной изменяемой ссылкой ( amp;mut T ), компилятор неявно выполняет повторное копирование, чтобы избежать перемещения изменяемой ссылки, потому amp;mut T что это не Copy так . К сожалению, эта «магия» компилятора не распространяется на Pin<amp;mut T> (чего тоже нет Copy ), поэтому вместо этого мы должны явно переродиться.

Самый простой способ переродиться-это использовать Pin::as_mut() . Этот вариант использования даже упоминается в документации:

Этот метод полезен при выполнении нескольких вызовов функций, использующих закрепленный тип.

 fn main() {
    let mut table = ffi::make_table();

    let mut row = table.pin_mut().get_row_ref();

    let_cxx_string!(hello="hello world");
    row.as_mut().set_value(amp;hello);

    let_cxx_string!(hello2="bye world");
    row.as_mut().set_value(amp;hello2);
}
 

Использование as_mut() при последнем использовании row не является строго необходимым, но последовательное применение, вероятно, более понятно. При компиляции с оптимизацией эта функция, вероятно, в любом случае является noop (для Pin<amp;mut T> ).

Как настроить API таким образом, чтобы ссылка на Row могла использоваться для нескольких последовательных вызовов методов (в пределах ограничений, установленных cxx )?

Если вы хотите скрыть as_mut() буквы «s», вы можете добавить метод, который принимает a amp;mut Pin<amp;mut ffi::Row> и выполняет as_mut() вызов. (Обратите внимание , что as_mut() это определено amp;mut Pin<P> , поэтому компилятор вставит повторное обновление внешнего amp;mut .) Да, это означает, что теперь существует два уровня косвенности.

 impl ffi::Row {
    pub fn set_value2(self: amp;mut Pin<amp;mut ffi::Row>, value: amp;cxx::CxxString) {
        self.as_mut().set_value(value)
    }
}

fn main() {
    let mut table = ffi::make_table();

    let mut row = table.pin_mut().get_row_ref();

    let_cxx_string!(hello="hello world");
    row.set_value2(amp;hello);

    let_cxx_string!(hello2="bye world");
    row.set_value2(amp;hello2);
}