WedX - журнал о программировании и компьютерных науках

Как мне преобразовать Vec ‹T› в Vec ‹U› без копирования вектора?

Я хочу преобразовать Vec<T> в Vec<U>, где T - какой-то примитив, а U - новый тип T: struct U(T).

Я пробовал примерно так:

struct Foo(u32);

fn do_something_using_foo(buffer: &mut Vec<Foo>) {}

fn main() {
    let buffer: Vec<u32> = vec![0; 100];

    do_something_using_foo(&mut buffer as Vec<Foo>);
}

Я не хочу делать копию вектора, я хочу обернуть поля u32 в новый тип Foo.

Это дает ошибку:

error[E0308]: mismatched types
 --> main.rs:8:28
  |
8 |     do_something_using_foo(&mut buffer as Vec<Foo>);
  |                            ^^^^^^^^^^^^^^^^^^^^^^^ expected mutable reference, found struct `std::vec::Vec`
  |
  = note: expected type `&mut std::vec::Vec<Foo>`
         found type `std::vec::Vec<Foo>`
  = help: try with `&mut &mut buffer as Vec<Foo>`

error: non-scalar cast: `&mut std::vec::Vec<u32>` as `std::vec::Vec<Foo>`
 --> main.rs:8:28
  |
8 |     do_something_using_foo(&mut buffer as Vec<Foo>);
  |                            ^^^^^^^^^^^^^^^^^^^^^^^

error: aborting due to previous error(s)
17.01.2018

  • Чтобы было ясно: вам нужно получить доступ к Vec<T> как &[U], &mut [U] или &mut Vec<U>? (Подсказка: варианты срезов намного проще). 17.01.2018
  • @bluss для моей ситуации &mut [U] было бы приемлемо, хотя &mut Vec<U> предпочтительнее. 17.01.2018
  • Если &mut [U] приемлемо, то это предпочтительнее &mut Vec<U>. 17.01.2018
  • Давным-давно было время, когда Vec имел fn map_in_place<U, F>(self, f: F) -> Vec<U> метод, который работал, когда T и U были одного размера. Он был удален в Rust 1.4, но, похоже, есть ящик, который это делает. Хотя в таком случае, наверное, перебор. 18.01.2018

Ответы:


1

Вы не можете изменить тип значения на месте в безопасном Rust. Нет гарантии, что два типа будут иметь одинаковый размер или одинаковую семантику.

Это относится как к одиночному значению (T -> U), так и к агрегированным значениям (Vec<T> -> Vec<U>, HashMap<K1, V1> -> HashMap<K2, V2>). Обратите внимание, что агрегированные значения на самом деле являются лишь частным случаем «одиночных» значений.


Лучше всего создать новый вектор:

let buffer2 = buffer.into_iter().map(Foo).collect();

Вы также можете настроить do_something_using_foo, чтобы он принимал общий универсальный тип, который реализуется как Foo, так и u32:

use std::borrow::{Borrow, BorrowMut};

#[derive(Debug, Clone)]
struct Foo(u32);

impl Borrow<u32> for Foo {
    fn borrow(&self) -> &u32 {
        &self.0
    }
}

impl BorrowMut<u32> for Foo {
    fn borrow_mut(&mut self) -> &mut u32 {
        &mut self.0
    }
}

fn do_something_using_foo<T>(buffer: &mut [T])
where
    T: BorrowMut<u32>,
{
}

fn main() {
    let mut buffer_u32 = vec![0u32; 100];
    let mut buffer_foo = vec![Foo(0); 100];

    do_something_using_foo(&mut buffer_u32);
    do_something_using_foo(&mut buffer_foo);
}

В небезопасном Rust это технически возможно - можно сколько угодно прострелить себе ногу.

Вы можете использовать что-то вроде std::mem::transmute, если знаете, что вы делает.

Однако неопределенное поведение - использовать transmute с Vec, поскольку представление Vec не определено. Вместо этого см. ответ Свена Марнака.

Смотрите также:

17.01.2018
  • Нет гарантии, что два типа будут иметь одинаковый размер. По крайней мере, это можно проверить: std::mem::size_of. 18.01.2018
  • Для безоперационного преобразования на месте вам не нужно ничего столь же сложного, как старый map_in_place(). Причина его сложности заключалась в основном в том, что ему приходилось иметь дело с промежуточными состояниями, когда части вектора уже имеют тип U, а остаток по-прежнему имеет тип T. Сделать это правильно даже в случае паники довольно сложно. Если исходный и целевой типы имеют одинаковую структуру памяти, и вы хотите только переинтерпретировать память как другой тип, то это можно сделать относительно легко. 10.03.2019
  • В документации говорится, что transmute эквивалентен memcpy в C под капотом, поэтому я не думаю, что это удовлетворяет критериям без копирования вектора. Я упоминаю об этом, потому что это высокий результат Google по трансмутации ржавчины без копирования. 10.05.2021
  • @detly да, копируются 24 байта (на 64-битной машине) Vec<T>. Данные не копируются. В противном случае вам понадобится что-то вроде преобразования 8 байтов &Vec<T>, что все равно будет включать копирование этих 8 байтов. 10.05.2021
  • Тогда я неправильно прочитал документы; Я думал, что он также скопирует весь нижележащий фрагмент T. Означает ли это, что преобразование среза копирует только данные, составляющие жирный указатель, а не непрерывный массив данных, на который он указывает? 10.05.2021
  • @detly да, это правильно. 10.05.2021

  • 2

    Согласно документации std::mem::transmute() при использовании Vec::from_raw_parts в сочетании с ManuallyDrop лучший вариант, начиная с Rust 1.38:

    let v_from_raw = unsafe {
        // Ensure the original vector is not dropped.
        let mut v_clone = std::mem::ManuallyDrop::new(v_orig);
        Vec::from_raw_parts(v_clone.as_mut_ptr() as *mut U,
                            v_clone.len(),
                            v_clone.capacity())
    };
    

    Предпосылкой для этого является то, что T и U имеют одинаковый размер, одинаковое минимальное выравнивание и что все битовые комбинации, действительные для T, также действительны для U. Если вы определите T и U как в своем вопросе, у вас нет гарантии на это.

    struct U(T) определяет структуру кортежа, и расположение памяти такой структуры полностью не определено. Однако можно заставить представления памяти быть идентичными, используя transparent представление:

    #[repr(transparent)]
    struct U(T);
    

    Будущие возможности

    Nightly Rust имеет Vec::into_raw_parts, который уменьшает количество кода и мест, где можно пойти не так:

    #![feature(vec_into_raw_parts)]
    
    fn convert_using_into_raw_parts(v: Vec<T>) -> Vec<U> {
        let (ptr, len, cap) = v.into_raw_parts();
        unsafe { Vec::from_raw_parts(ptr as *mut U, len, cap) }
    }
    

    Также существует открытый документ RFC Collection Transmute # 2756, в котором предлагается добавить метод Vec::transmute.

    09.03.2019
    Новые материалы

    Как создать диаграмму градиентной кисти с помощью D3.js
    Резюме: Из этого туториала Вы узнаете, как добавить градиентную кисть к диаграмме с областями в D3.js. Мы добавим градиент к значениям SVG и применим градиент в качестве заливки к диаграмме с..

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

    Лицензии с открытым исходным кодом: руководство для разработчиков и создателей
    В динамичном мире разработки программного обеспечения открытый исходный код стал мощной парадигмой, способствующей сотрудничеству, инновациям и прогрессу, движимому сообществом. В основе..

    Объяснение документов 02: BERT
    BERT представил двухступенчатую структуру обучения: предварительное обучение и тонкая настройка. Во время предварительного обучения модель обучается на неразмеченных данных с помощью..

    Как проанализировать работу вашего классификатора?
    Не всегда просто знать, какие показатели использовать С развитием глубокого обучения все больше и больше людей учатся обучать свой первый классификатор. Но как только вы закончите..

    Работа с цепями Маркова, часть 4 (Машинное обучение)
    Нелинейные цепи Маркова с агрегатором и их приложения (arXiv) Автор : Бар Лайт Аннотация: Изучаются свойства подкласса случайных процессов, называемых дискретными нелинейными цепями Маркова..

    Crazy Laravel Livewire упростил мне создание электронной коммерции (панель администратора и API) [Часть 3]
    Как вы сегодня, ребята? В этой части мы создадим CRUD для данных о продукте. Думаю, в этой части я не буду слишком много делиться теорией, но чаще буду делиться своим кодом. Потому что..


    Для любых предложений по сайту: [email protected]