[Solved] Need help with typing & ownership when taking an array and returning an array of array slices


#1

I want a chunks function that takes an array and returns an array of slices of the array.

  • First parameter is (a ref for) the array.

  • Second parameter is the length of the first chunk.

  • Third parameter is the length of subsequent chunks.

  • A 0 {second, third} parameter means “just pass a slice of the rest”.

Say l = [1,2,3,4,5], then:

chunks(l, 0, 0) -> [ [1,2,3,4,5] ]
chunks(l, 1, 0) -> [ [1], [2,3,4,5] ]
chunks(l, 1, 1) -> [ [1], [2], [3], [4], [5] ]
chunks(l, 1, 3) -> [ [1], [2,3,4], [5] ]
chunks(l, 2, 0) -> [ [1,2], [3,4,5] ]
chunks(l, 2, 1) -> [ [1,2], [3], [4], [5] ]
chunks(l, 2, 2) -> [ [1,2], [3,4], [5] ]
etc...

So, I try:

fn chunks(list: &[i32], first_chunk: i32, later_chunks: i32) -> [&[i32]] {
    [list] // stub implementation, to be implemented after compiler is happy
}

But rustc complains about the output type:

the size for values of type [&[i32]] cannot be known at compilation time

Which makes sense. So let’s return a reference, right?

fn chunks_2(list: &[i32], first_chunk: i32, later_chunks: i32) -> &[&[i32]] {
    &[list] // stub implementation, to be implemented after compiler is happy
}

But compilation now fails with:

cannot return reference to temporary value
returns a reference to data owned by the current function

…further detailed by rustc --explain E0515, which says:

Consider returning an owned value instead

Uh but yes, I do want to return a reference, because I don’t know the size of the output, because of step 1! And surely enough, if I come back to returning a value, then I get back my initial error that the size for values of type [&[i32]] cannot be known at compilation time :confused:. What should I do?

  • Is this a case where I should take a mutable reference where my function would write, à la C?
  • Should I look into iterators? (I want to, but first I wanted to try a simpler approach with just a function).
  • Something entirely else?

I’m running rust 1.31.1. Thanks for the help :slightly_smiling_face:.


#2

If you don’t mind the allocation, return a Vec. Generally though, iterators are best & awesome.

Your “problem” is that slices are not dynamically sized, but since you want to use 1 function for all possible combinations, you need something that’s dynamically sized. I.e., a Vec.


#3

Rust has fixed-size arrays and variable-size slices, and both use [] in their syntax, so be careful, because they get mixed up.

Unlike other languages where arrays are the basic data type, in Rust the arrays are generally useless, and it’s better to stay away from them. Use slices and Vec.

Raw slice such as [] or str has unknown size, because the size is stored in the reference (&) to the slice, not in the slice. If you have a slice &[1,2,3] then [1,2,3] refers to 3 numbers in the memory (it’s literally something like 010203 value), and &[1,2,3] has a fixed size, which is 2 elements: (length=3, data=stored over there).

You can’t return something of any possible unspecified length from a function, because the calling function wouldn’t know how much of the data to take.

And when function returns a reference (e.g. &[u8]), it can’t be a reference to any newly-created object. References can only exist while they point to something already permanently stored elsewhere. If you create something in the function, it’s temporary for that function call, so gone before you can return it. In practice you either return a reference to data in one of function’s arguments, or return a new owned object like Vec.


#4

Thanks for the fast answers :slightly_smiling_face:.

Super confusing indeed! And it was my intention to return an slice of slices. What in my current code makes the return value not a slice (of slices)? The lack of a & reference token, right? So reformulating what both of you wrote, to fixate thoughts:

  • Using a slice as container for a return value doesn’t work, because slices are fundamentally reference-based and I cannot return a reference to something created in the function.
  • Returning [list] is impossible since it would have to be a fixed-size array of slice(s), whose size rustc cannot determine.
  • I should just return a Vec (or if trying to avoid allocation at all costs, be passed a mutable reference).

Also, when you correct me about slices & arrays, you talk about the outer container array, right? Each of the inner slices can and should be slices, as efficient {start pointer, quantity} “views” into my input list, right? Put differently again, the inner slices are alright (in my stub implementation there is just one slice), it’s the outer array that I should just replace with a Vec. Right?

Okay. The extra allocation is fine, and now I understand my confusion: I was thinking that the fact my return value was comprised of slices into the list parameter made it “not new” (and the stub implementation hid the fact I was creating a new value and immediately returning it, upcoming real code would have had at least one let). Wrong, it remains a new value. :+1:.

EDIT: also, now I’m wondering: should I keep the list parameter a slice? Here my sample code is just a toy implementation with integers, but ultimately for my needs, the inner things are going to be structures, not integers. Should I put them in a Vec too? If so, can I efficiently return slices that represent a chunk of it?


#5

From how I understood your needs: Yes. A slice is strictly more general then a reference to a Vec, since &Vec<T> coerces to a &[T] (meaning, whenever you need to pass a &[T], a &Vec<T> will do).

If you don’t statically know how many of them you have, that’s basically your only option (unless allocating a large array and maybe not filling all of it).

Not sure what you mean by it, but it can be as easy as &v[2..3] where v: Vec<Struct>. Your chunk function might have signature chunks(list: &[Struct], ... ) -> Vec<&[Struct]>.


#6

&[ &[1,2,3] ] literal is syntactically ambiguous. It could be a reference to a 1-element array of references to 3-element arrays, or a slice of slices, or combination of both. But if you assign that to a variable with an explicit type, Rust will figure it out.

Note that a slice is a view of data that has existed before it. So to make a slice, you first need either another slice, or Vec, Box or other container that can be viewed as a slice. To return a slice of slices, there has to already exist some container of slices before your function is called.

You can return a Vec of slices, since you can make a new owned Vec, and the slices inside it can point to pre-existing data.


#7

All clear :slightly_smiling_face:. Thaaaaaaaaa-anks @KillTheMule @kornel !