#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
-для этого и предназначена оболочка.
У меня есть две проблемы:
- Правильно ли это, что я звоню
Pin::new_unchecked
сюда? Из документации следует, что это не так:
вызов Pin-кода::new_unchecked для amp;’a mut T небезопасен, потому что, хотя вы можете закрепить его на данный срок службы ‘a, вы не можете контролировать, будет ли он закреплен один раз ‘a заканчивается
Если это небезопасно, как мне действовать?
- Эта программа не может быть скомпилирована со следующей ошибкой:
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:
- Правильно ли это, что я звоню
Pin::new_unchecked
сюда?
Да, это звучит убедительно. В этом контексте мы знаем Row
, что это закреплено, потому что:
- Тот
Table
приколот; - Хранится
Row
встроенным вTable
; - Семантика перемещения C , по сути, означает, что каждый объект C в любом случае «закреплен».
- Эта программа не может быть скомпилирована со следующей ошибкой:
Когда вы вызываете метод с обычной изменяемой ссылкой ( 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);
}