Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I convert a Vec<T> to a Vec<U> without copying the vector?

Tags:

rust

I want to convert a Vec<T> to a Vec<U> where T is a primitive of some sort and U is a newtype of T: struct U(T).

I tried something like this:

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>);
}

I don't want to make a copy of the vector, I want to wrap the u32 fields in the newtype Foo.

This gives the error:

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)
like image 385
tversteeg Avatar asked Jan 17 '18 19:01

tversteeg


2 Answers

You cannot change the type of a value in place in safe Rust. There is no guarantee that the two types will have the same size, alignment, or semantics.

This applies to a single value (T -> U) as well as aggregate values (Vec<T> -> Vec<U>, HashMap<K1, V1> -> HashMap<K2, V2>). Note that aggregate values are really just a special case of "single" values.

Create a new vector

The most straight-forward solution:

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

In cases where the compiler can tell that you aren't actually changing important things, this becomes effectively a no-op:

fn convert(b: Vec<u32>) -> Vec<Foo> {
    b.into_iter().map(Foo).collect()
}

x86_64 assembly output for Rust 1.54 with optimizations:

playground::convert:
    movq    %rdi, %rax
    movq    (%rsi), %rcx
    movups  8(%rsi), %xmm0
    movq    %rcx, (%rdi)
    movups  %xmm0, 8(%rdi)
    retq

Unfortunately, this lightweight transformation isn't currently a guaranteed property, only an implementation detail.

Use generics

You could adjust do_something_using_foo to take in a generic type and have both Foo and u32 implement a trait:

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);
}

Unsafe Rust

It's technically possible — you can shoot yourself in the foot as much as you'd like.

You can use something like std::mem::transmute if you know what you are doing.

However, it's undefined behavior to use transmute with Vec as the representation of Vec is not defined. Instead, see Sven Marnach's answer.

See also:

  • Using map with Vectors
  • Converting a Vec<u32> to Vec<u8> in-place and with minimal overhead
like image 131
Shepmaster Avatar answered Sep 21 '22 12:09

Shepmaster


According to the documentation of std::mem::transmute(), using Vec::from_raw_parts combined with ManuallyDrop is the best option, as of 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())
};

The prerequisite for this is that T and U have the same size, the same minimum alignment and that all bit patterns that are valid for T are also valid for U. If you define T and U as in your question, you don't have a guarantee for this.

struct U(T) defines a tuple struct, and the memory layout of such a struct is completely undefined. However, it is possible to force the memory representations to be identical by using the transparent representation:

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

Future options

Nightly Rust has Vec::into_raw_parts, which reduces the amount of code and places to go wrong:

#![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) }
}

There's also an open RFC Collection Transmute #2756 which proposes adding a Vec::transmute method.

like image 21
Sven Marnach Avatar answered Sep 22 '22 12:09

Sven Marnach