#rust #ffi
#Ржавчина #ffi
Вопрос:
Я оборачиваю более старую библиотеку C, которая требует, чтобы определенные объекты существовали только один раз в любой данный момент времени во время выполнения программы.
Возможно ли в Rust гарантировать такое поведение для структур во время компиляции?
Или я должен изучить, как создавать синглтоны и, возможно, передавать Arc<MyWrapperStruct>
их?
Я изучил std::sync::Once
, но это похоже на инструмент для создания чего-то вроде синглтона или для обеспечения того, чтобы что-то происходило не более одного раза в течение срока службы приложения.
Это вполне нормально для MyWrapperStruct
создания экземпляров несколько раз, но компилятор должен гарантировать MyWrapperStruct
, что они никогда не существуют одновременно (разные потоки) или в одной и той же области, как-то дважды.
Последующие экземпляры MyWrapperStruct
являются законными, если предыдущие экземпляры были удалены и вышли из области видимости.
Пример
pub struct MyWrapperStruct<'base> {
pub base: amp;'base mut libc::c_void,
}
impl<'base> MyWrapperStruct<'base> {
pub fn new(logfile: amp;str) -> MyWrapperStruct<'base> {
let string = CString::new(logfile).unwrap();
let mut base: amp;mut libc::c_void;
unsafe {
base = amp;mut *ptr::null_mut();
// c-call here
call_to_c_lib(amp;mut base, string.as_ptr());
}
MyWrapperStruct { base }
}
}
fn should_not_compile() {
MyWrapperStruct::new("log1.txt");
MyWrapperStruct::new("log2.txt");
}
fn should_compile() {
{
MyWrapperStruct::new("log1.txt");
}
{
MyWrapperStruct::new("log2.txt");
}
}
Комментарии:
1. «Совершенно нормально, что экземпляр MyWrapperStruct создается несколько раз, но компилятор должен гарантировать, что MyWrapperStruct никогда не существует одновременно (разные потоки)» Как компилятор будет проверять во время компиляции то, что кажется настолько динамичным?
2. @mcarton Да. Возможно, ответ на это требование «разных потоков» заключается в том, что Rust-компилятор не может этого сделать, и я должен изучить, как создать синглтон этой структуры и передать дугу вокруг, или что-то в этом роде. Возможно, мне следует использовать другой подход для безопасной упаковки этой C-библиотеки. Отсюда вопрос 😉
3. Я бы согласился с Маккартоном, это очень динамичное поведение, компилятор не может помочь, и для этого, вероятно, требуется какая-то глобальная блокировка (с использованием
lazy_static!
илиonce_cell
), удерживаемая структурой в течение ее жизненного цикла, и ctor должен быть отказоустойчивым (в противном случае вы зайдете в тупик при попытке создать структуру, пока одинявляется живой).4. Даже использование блокировки в качестве токена здесь не поможет, потому что для блокировки мьютекса требуется только ref’ , которых у вас может быть несколько, а средство проверки заимствования не может помочь вам с изменяемой статикой (они небезопасны по определению). Альтернативой было бы создать «токен» в начале программы и развернуть его, при этом инициализатор требует владения токеном и деинициализации, отказывающейся от токена (можно использовать изменяемый ref’ вместо владения, преимущество в том, что вы не можете «потерять» токен, недостаток — перемещение вашегоструктура была бы намного сложнее)
5.
amp;mut *ptr::null_mut()
мгновенный UB. Очень маловероятно, что ссылка со временем жизни — это то, что вам нужноbase
. ЕслиMyWrapperStruct
предполагается, что он представляет право собственности на базовый тип C, он должен просто содержать*mut c_void
или, возможноNonNull<c_void>
, и не иметь параметра времени жизни.
Ответ №1:
Вы не можете сделать это во время компиляции, однако во время выполнения вы можете сделать это с помощью относительно простого атомарного трекера.
use std::sync::atomic::{AtomicBool, Ordering};
static INSTANCE_EXISTS: AtomicBool = AtomicBool::new(false);
pub struct MyStruct {}
impl MyStruct {
pub fn try_new() -> Result<Self, amp;'static str> {
if !INSTANCE_EXISTS.compare_and_swap(false, true, Ordering::SeqCst) {
// Placeholder for C-side create object code
Ok(MyStruct {})
} else {
Err("Instance of MyStruct currently exists")
}
}
}
impl Drop for MyStruct {
fn drop(amp;mut self) {
// C-side destroy object code here
INSTANCE_EXISTS.store(false, Ordering::Release);
}
}
Обратите внимание, что если создание MyStruct
can завершится неудачей, вам нужно будет выполнить дополнительную бухгалтерию, чтобы либо отменить блокировку, либо отравить ее (т. Е. сигнализировать программе о катастрофическом сбое, и новая MyStruct
не может быть создана).
В качестве небольшого отступа для решения вашей конкретной проблемы, хранение уникального указателя на значение на стороне C является собственностью, а не ссылкой. По сути, это просто Box
. Вам не нужно время жизни или ссылка там, вы хотите напрямую удерживать необработанный указатель, вероятно, как NonNull
.
Комментарии:
1. Примечание: я не уверен на 100% в своих
Ordering
значениях, потому что я редко использую необработанную атомарную базу, поэтому, если кто-нибудь увидит какие-либо проблемы, пожалуйста, отредактируйте!2. Спасибо за ваш подробный ответ, пример и подсказки, как обращаться с моим значением на стороне c!