Как безопасно переосмыслить Vec <f64> как Vec <num_complex :: Complex <f64 >> с половиной размера? - PullRequest
0 голосов
/ 14 января 2019

У меня есть данные комплексных чисел, заполненные Vec<f64> внешней библиотекой C (предпочитают не изменять) в форме [i_0_real, i_0_imag, i_1_real, i_1_imag, ...], и кажется, что этот Vec<f64> имеет ту же структуру памяти, что и Vec<num_complex::Complex<f64>> из половина длины была бы, учитывая, что структура данных num_complex::Complex<f64> совместима с макетом памяти с [f64; 2], как задокументировано здесь . Я хотел бы использовать его как таковой без необходимости перераспределения потенциально большого буфера.

Я предполагаю, что допустимо использовать from_raw_parts() в std::vec::Vec, чтобы подделать новый Vec, который берет на себя владение старой памятью Vec (забыв старый Vec), и использовать size / 2 и capacity / 2, но для этого требуется небезопасный код. Есть ли «безопасный» способ сделать такую ​​интерпретацию данных?

Vec выделяется в Rust как Vec<f64> и заполняется функцией C с использованием .as_mut_ptr(), которая заполняет Vec<f64>.

Моя текущая компиляция небезопасной реализации:

extern crate num_complex;

pub fn convert_to_complex_unsafe(mut buffer: Vec<f64>) -> Vec<num_complex::Complex<f64>> {
    let new_vec = unsafe {
        Vec::from_raw_parts(
            buffer.as_mut_ptr() as *mut num_complex::Complex<f64>,
            buffer.len() / 2,
            buffer.capacity() / 2,
        )
    };
    std::mem::forget(buffer);
    return new_vec;
}

fn main() {
    println!(
        "Converted vector: {:?}",
        convert_to_complex_unsafe(vec![3.0, 4.0, 5.0, 6.0])
    );
}

1 Ответ

0 голосов
/ 14 января 2019

Есть ли "безопасный" способ выполнить такую ​​интерпретацию данных?

Нет. По крайней мере, это потому, что информация, которую вам нужно знать, не выражается в системе типов Rust, а выражается в прозе (a.k.a. docs):

Complex<T> совместима с массивом памяти с массивом [T; 2].

- Complex документы

Если Vec выделил память, то [...] его указатель указывает на len инициализированных смежных элементов по порядку (что вы увидите, если вы приведете его к срезу),

- Vec документы

Массивы приводят к кусочкам ([T])

- Array docs

Поскольку Complex совместим с памятью с массивом, данные массива совместимы с срезом по памяти, а данные Vec совместимы с срезом по памяти, это преобразование должно быть безопасным, даже хотя компилятор не может сказать это.

Эта информация должна быть прикреплена (через комментарий) к вашему небезопасному блоку.

Я сделает небольшие изменения в вашей функции:

  • Наличие двух Vec с одновременно указывающих на одни и те же данные заставляет меня очень нервничать. Этого можно легко избежать, введя некоторые переменные и забыв один перед созданием другого.

  • Удалите ключевое слово return для большей идиоматичности

  • Добавьте некоторые утверждения, что начальная длина данных кратна двум.

  • Как указывает Родриго , емкость может легко быть нечетным числом. Чтобы попытаться избежать этого, мы вызываем shrink_to_fit. Это имеет недостаток в том, что Vec может необходимо перераспределить и скопировать память, в зависимости от реализации.

  • Разверните блок unsafe, чтобы охватить весь связанный код, необходимый для обеспечения соблюдения защитных инвариантов.

pub fn convert_to_complex(mut buffer: Vec<f64>) -> Vec<num_complex::Complex<f64>> {
    // This is where I'd put the rationale for why this `unsafe` block
    // upholds the guarantees that I must ensure. Too bad I
    // copy-and-pasted from Stack Overflow without reading this comment!
    unsafe {
        buffer.shrink_to_fit();

        let ptr = buffer.as_mut_ptr() as *mut num_complex::Complex<f64>;
        let len = buffer.len();
        let cap = buffer.capacity();

        assert!(len % 2 == 0);
        assert!(cap % 2 == 0);

        std::mem::forget(buffer);

        Vec::from_raw_parts(ptr, len / 2, cap / 2)
    }
}

Чтобы не беспокоиться о емкости, вы можете просто преобразовать срез в Vec. Это также не имеет никакого дополнительного выделения памяти. Это проще, потому что мы можем «потерять» любые нечетные конечные значения, потому что Vec все еще поддерживает их.

pub fn convert_to_complex(buffer: &[f64]) -> &[num_complex::Complex<f64>] {
    // This is where I'd put the rationale for why this `unsafe` block
    // upholds the guarantees that I must ensure. Too bad I
    // copy-and-pasted from Stack Overflow without reading this comment!
    unsafe {
        let ptr = buffer.as_ptr() as *mut num_complex::Complex<f64>;
        let len = buffer.len();

        std::slice::from_raw_parts(ptr, len / 2)
    }
}
...