Как я могу использовать PhantomData в структуре с необработанными указателями, чтобы структура не переживала время существования другой структуры, на которую ссылаются? - PullRequest
0 голосов
/ 20 октября 2018

У меня есть структура, которая имеет небезопасный код и необработанные изменяемые указатели на другой тип структуры.Небезопасная структура должна использоваться только во время существования другой структуры, но вы не можете указать время жизни для указателей.Я обнаружил, что std::marker::PhantomData может быть использовано для решения этих неиспользованных проблем в течение всей жизни, но у меня возникают проблемы с его работойЯ не уверен, что это неверный вариант использования или я делаю что-то не так.

Упрощенный пример :

use std::marker::PhantomData;

pub struct Test {
    value: u32,
}

impl Test {
    pub fn value(&self) {
        println!("{}", self.value)
    }

    pub fn set_value(&mut self, value: u32) {
        self.value = value;
    }
}

// I want compiler to complain about the lifetime of test
// so that UnsafeStruct is not used after test is dropped
pub struct UnsafeStruct<'a> {
    test: *mut Test,
    phantom: PhantomData<&'a mut Test>,
}

impl<'a> UnsafeStruct<'a> {
    pub fn new(test: &'a mut Test) -> UnsafeStruct<'a> {
        UnsafeStruct {
            test: test,
            phantom: PhantomData,
        }
    }

    pub fn test_value(&self) {
        unsafe { println!("{}", (*self.test).value) }
    }

    pub fn set_test_value(&mut self, value: u32) {
        unsafe {
            (*self.test).set_value(value);
        }
    }
}

fn main() {
    // No borrow checker errors
    // but the compiler does not complain about lifetime of test
    let mut unsafe_struct: UnsafeStruct;
    {
        let mut test = Test { value: 0 };
        unsafe_struct = UnsafeStruct {
            test: &mut test,
            phantom: PhantomData,
        };

        unsafe_struct.set_test_value(1);
        test.value();

        test.set_value(2);
        unsafe_struct.test_value();
    }
    unsafe_struct.set_test_value(3);
    unsafe_struct.test_value();

    // Lifetime errors caught
    // but there will be borrow checker errors if you fix
    let mut unsafe_struct: UnsafeStruct;
    {
        let mut test = Test { value: 0 };
        unsafe_struct = UnsafeStruct::new(&mut test);

        unsafe_struct.set_test_value(1);
        test.value();

        test.set_value(2);
        unsafe_struct.test_value();
    }
    unsafe_struct.set_test_value(3);
    unsafe_struct.test_value();

    // Borrow checker errors when you fix lifetime error
    {
        let mut test = Test { value: 0 };
        let mut unsafe_struct: UnsafeStruct;
        unsafe_struct = UnsafeStruct::new(&mut test);

        unsafe_struct.set_test_value(1);
        test.value();

        test.set_value(2);
        unsafe_struct.test_value();
    }
}

Если я создаю UnsafeStruct непосредственно компилятор не перехватывает ошибки времени жизни, и я все равно хотел бы использовать функцию конструктора.Если я использую функцию конструктора, я получаю ошибки проверки заимствования.Можно ли исправить этот код так, чтобы компилятор выдавал ошибку при попытке использовать UnsafeStruct вне времени жизни соответствующего Test, но не имел ошибок проверки заимствования, показанных в примере?

Ответы [ 2 ]

0 голосов
/ 21 октября 2018

Я отвечаю на свой вопрос.Проблема, которую я пытался решить, заключалась в использовании std::marker::PhantomData для добавления времени жизни к структуре с необработанными указателями для предотвращения использования после свободных ошибок.Вы не можете достичь этого с PhantomData.Существует вариант использования для обработки необработанных жизней, но он отличается от того, что я пытался выполнить, и был источником моего замешательства / вопроса.

Я уже знал и учел тот факт, что вы должны справитьсяиспользовать после свободных и других ошибок при использовании небезопасного кода.Я просто думал, что смогу справиться с этим типом использования после свободной ошибки во время компиляции, а не во время выполнения.

0 голосов
/ 20 октября 2018

TL; DR То, что вы делаете, нарушает требование эксклюзивности для изменяемых ссылок, но вы можете использовать общие ссылки и внутреннюю изменчивость для создания API, который работает.

A &mut T ссылка представляет эксклюзивный доступ к T.Когда вы заимствуете объект с &mut, этот объект не должен быть доступен (изменяемым или неизменным) через любую другую ссылку в течение срока действия заимствования &mut.В этом примере:

let mut test = Test { value: 0 };
let mut unsafe_struct: UnsafeStruct;
unsafe_struct = UnsafeStruct::new(&mut test);

unsafe_struct.set_test_value(1);
test.value();

test.set_value(2);
unsafe_struct.test_value();

unsafe_struct поддерживает заем &mut test в живых.Неважно, что внутри он содержит необработанный указатель;это не может содержать ничего.'a в UnsafeStruct<'a> продлевает срок действия заимствования, делая его неопределенным поведением для прямого доступа к test до тех пор, пока unsafe_struct не будет использован в последний раз.

В примере предполагается, что вы на самом делехотите общий доступ доступ к ресурсу (то есть общий доступ между test и unsafe_struct).Rust имеет общий ссылочный тип;это &T.Если вы хотите, чтобы оригинал T оставался доступным в течение срока действия заимствования, этот заем имеет для совместного использования (&), а не эксклюзивный (&mut).

Как вы изменяете что-то, если у вас есть общая ссылка?Использование внутренней изменчивости .

use std::cell::Cell;

pub struct Test {
    value: Cell<u32>,
}

impl Test {
    pub fn value(&self) {
        println!("{}", self.value.get())
    }

    pub fn set_value(&self, value: u32) {
        self.value.set(value);
    }
}

pub struct SafeStruct<'a> {
    test: &'a Test,
}

impl<'a> SafeStruct<'a> {
    pub fn new(test: &'a Test) -> SafeStruct<'a> {
        SafeStruct { test }
    }

    pub fn test_value(&self) {
        println!("{}", self.test.value.get())
    }

    pub fn set_test_value(&self, value: u32) {
        self.test.set_value(value);
    }
}

Не осталось кода unsafe - Cell - безопасная абстракция.Вы также можете использовать AtomicU32 вместо Cell<u32> для обеспечения безопасности потоков или, если реальное содержимое Test более сложное, RefCell, RwLock или Mutex.Все это абстракции, которые обеспечивают общую («внутреннюю») изменчивость, но они различаются по использованию.Для получения более подробной информации ознакомьтесь с документацией и ссылками ниже.

В качестве окончательного решения, если вам нужен общий изменяемый доступ к объекту без накладных расходов, и вы несете полную ответственность за обеспечение его правильности на своих плечах, вы можетеиспользуйте UnsafeCell.Этот требует , требует использования unsafe кода, но вы можете написать любой API, какой захотите.Обратите внимание, что все упомянутые мною безопасные абстракции построены с использованием UnsafeCell внутри.Вы не можете иметь общую изменчивость без нее.

Ссылки

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...