1

I have a container:

pub struct Foo<T> {
    pub data: Box<[T]>,
}

I would like a method to initialize a new one from an existing slice:

impl<T> Foo<T> {
    fn from_slice(slice: &[T]) -> Foo<T> {
        Foo {
            data: Box::new(/* something here */),
        }
    }
}

I'd like to create a Foo instance from any kind of slice, coming from a dynamic vector or a static string.

I suppose there is a reason why vec! is a macro, but is there a way to avoid writing one? I guess I could do slice.to_vec().into_boxed_slice(), but it doesn't seem right to create a Vec as a proxy to a clone...

I'm not using a Vec in my struct because the data isn't supposed to change in size during the lifetime of my container. It didn't feel right to use a Vec but I may be wrong.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Simon
  • 860
  • 7
  • 23
  • 1
    I really don't understand please always share your goal of doing thing. Why not just `pub data: Vec` ? – Stargateur May 30 '19 at 07:11
  • The data attribute isn't supposed to grow or shrink during the lifetime of my container, so it just didn't feel right. – Simon May 30 '19 at 07:25
  • 1
    To prevent changing the size you shouldn't make the field public. You can implement `IndexMut` if you want to allow to change values. – starblue May 30 '19 at 07:39
  • Thanks ! It's more that I didn't want any overhead... In C++ I wouldn't use an `std::vector` when a simpler type does the job. Bad habits from a poor background I guess. ;) – Simon May 30 '19 at 07:45
  • The array class you're referring to is statically sized, which doesn't fit every cases either. As for raw pointers... they are the only options you have in many situations where 1) you want the softer syntax of C++ over C and 2) can't afford using the STL and/or dynamic allocations for every bit of code you write. But thanks for the condescending tone, it's always appreciated. I'll have a look at ndarray's implementation as a reference for my toy program, thanks. – Simon May 30 '19 at 09:30
  • (Comment above was intended to @Stargateur, who deleted his) – Simon May 30 '19 at 11:22

1 Answers1

5

If your slice contains Copy types, you can use From / Into to perform the construction:

pub struct Foo<T> {
    pub data: Box<[T]>,
}

impl<T> Foo<T> {
    fn from_slice(slice: &[T]) -> Foo<T>
    where
        T: Copy,
    {
        Foo { data: slice.into() }
    }
}

If your data is Clone, then you can use to_vec + into_boxed_slice:

impl<T> Foo<T> {
    fn from_slice(slice: &[T]) -> Foo<T>
    where
        T: Clone,
    {
        Foo { data: slice.to_vec().into_boxed_slice() }
    }
}

it doesn't seem right to create a Vec as a proxy to a clone

You aren't cloning here. When you clone a type T, you get a type T back. You are starting with a &[T] and want to get a Box<[T]>, not a [T] (which you can't have).

Creating a boxed slice through a Vec means that you temporarily take up 3 machine-sized integers instead of 2; this is unlikely to be a performance problem compared to the amount of allocation performed.

I do agree with starblue's answer that keeping a Vec<T> is probably simpler for most cases, but I admit that there are times where it's useful to have a boxed slice.

See also:

I suppose there is a reason why vec! is a macro

The implementation of vec! is public:

macro_rules! vec {
    ($elem:expr; $n:expr) => (
        $crate::vec::from_elem($elem, $n)
    );
    ($($x:expr),*) => (
        <[_]>::into_vec(box [$($x),*])
    );
    ($($x:expr,)*) => (vec![$($x),*])
}

It's really only a macro for syntax convenience (and because it uses the unstable box keyword); it takes the arguments, creates an array, boxes it, coerces it to a boxed slice, then converts it to a Vec.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366