Можно ли гарантировать во время компиляции, что данная структура существует не более одного раза в любой данный момент времени в течение жизненного цикла программы?

#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!