Запись в поле в структуре MaybeUninit? - PullRequest
4 голосов
/ 20 апреля 2020

Я делаю что-то с MaybeUninit и FFI в Rust, что, кажется, работает, но я подозреваю, что это может быть неправильно или полагаться на неопределенное поведение.

Моя цель - иметь структуру MoreA extension структура A, включая A в качестве начального поля. А затем вызвать некоторый код C, который пишет в структуру A. А затем завершите MoreA, заполнив его дополнительные поля, основываясь на том, что в A.

В моем приложении все дополнительные поля MoreA являются целыми числами, поэтому мне не о чем беспокоиться о назначениях для них, отбрасывая (неинициализированные) предыдущие значения.

Вот минимальный пример:

use core::fmt::Debug;
use std::mem::MaybeUninit;

#[derive(Clone, Copy, PartialEq, Debug)]
#[repr(C)]
struct A(i32, i32);

#[derive(Clone, Copy, PartialEq, Debug)]
#[repr(C)]
struct MoreA {
    head: A,
    more: i32,
}

unsafe fn mock_ffi(p: *mut A) {
    // write doesn't drop previous (uninitialized) occupant of p
    p.write(A(1, 2));
}

fn main() {
    let mut b = MaybeUninit::<MoreA>::uninit();
    unsafe { mock_ffi(b.as_mut_ptr().cast()); }
    let b = unsafe {
        let mut b = b.assume_init();
        b.more = 3;
        b
    };
    assert_eq!(&b, &MoreA { head: A(1, 2), more: 3 });
}

Звучит ли код let b = unsafe { ... }? Он работает нормально и Мири не жалуется .

Но MaybeUninit документы говорят:

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

Кроме того, в книге Rust говорится, что Поведение считается неопределенным включает в себя:

  • Создание недопустимого значения, даже в частных полях и локальных. «Создание» значения происходит всякий раз, когда значение присваивается или читается из места, передается функции / примитивной операции или возвращается из функции / примитивной операции. Следующие значения недопустимы (в соответствующем типе):

    ... Целое число (i * / u *) или ..., полученное из неинициализированной памяти.

С другой стороны, представляется невозможным записать в поле more 1047 * до вызова assume_init. Позже на той же странице:

В настоящее время не поддерживается способ создания необработанного указателя или ссылки на поле структуры внутри MaybeUninit. Это означает, что невозможно создать структуру, вызвав MaybeUninit :: uninit: :() и затем записав его в поля.

Если то, что я делаю в приведенном выше примере кода вызывает неопределенное поведение, какими будут решения?

  1. Я бы хотел не включать в бокс значение A (то есть, я бы хотел, чтобы оно было непосредственно включено в MoreA).

  2. Я бы также хотел избежать создания одного A для передачи на mock_ffi, а затем копирования результатов в MoreA. A в моем реальном приложении - это большая структура.

Я думаю, если бы не было никакого здравого способа получить то, чего я добиваюсь, я бы выбрал один из них. два запасных варианта.

Если структура A относится к типу, который может содержать битовую комбинацию 0 в качестве допустимого значения, то я думаю, что третий запасной вариант будет:

Начните с MaybeUninit::zeroed() вместо MaybeUninit::uninit().

1 Ответ

3 голосов
/ 20 апреля 2020

В настоящее время единственным надежным способом обращения к неинициализированной памяти любого типа является MaybeUninit. На практике , вероятно, безопасно для чтения или записи неинициализированных целых чисел, но это официально не задокументировано. Определенно не безопасно читать или записывать неинициализированный bool или большинство других типов.

В целом, как указано в документации, вы не можете инициализировать поле структуры по полю. Тем не менее, это разумно, если:

  1. структура имеет repr(C). Это необходимо, поскольку он не позволяет Rust выполнять хитрые уловки макетов, поэтому макет поля типа MaybeUninit<T> остается идентичным макету поля типа T независимо от смежных полей.
  2. каждое поле равно MaybeUninit. Это позволяет нам assume_init() для всей структуры, а затем инициализировать каждое поле индивидуально.

Учитывая, что ваша структура уже repr(C), вы можете использовать промежуточное представление, которое использует MaybeIninit для каждое поле. repr(C) также означает, что мы можем преобразовывать типы после его инициализации, при условии, что две структуры имеют одинаковые поля в одном и том же порядке.

use std::mem::{self, MaybeUninit};

#[repr(C)]
struct MoreAConstruct {
    head: MaybeUninit<A>,
    more: MaybeUninit<i32>,
}

let b: MoreA = unsafe {
    // It's OK to assume a struct is initialized when all of its fields are MaybeUninit
    let mut b_construct = MaybeUninit::<MoreAConstruct>::uninit().assume_init();
    mock_ffi(b_construct.head.as_mut_ptr());
    b_construct.more = MaybeUninit::new(3);
    mem::transmute(b_construct)
};
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...