Returning an empty array from a function

Hello there,

I was wondering if there's a way to returning an empty array or an array with a single element from a function?

I currently have an enum like this:

#[derive(Clone, PartialEq, Eq, Debug)]
pub enum Maybe<T> {
   Nothing,
   Just(T),
}

And a function:

    pub fn maybe_to_list(maybe: Maybe<T>) -> [T;1] {
        match maybe {
           Maybe::Nothing => [],
           Maybe::Just(val) => [val]
        }
   }

But, an error occurs with that function (sorry for the picture, I can't format it correctly here).

If the value of the enum is Nothing, it should return an empty array. If not, it returns an array of a single element with the value of Just. Is there any way to do that? Thanks for the help!

What's against using Option?

fn option_to_array<T>(maybe: Option<T>) -> Option<[T;1]> {
  maybe.map(|t| [t])
}

Yeah, I could use an option, but that's not really what I want. There's really no other solution?

Hi, you can do it like that. What's happening is that you made your return type a slice of size 1, but then you return an empty slice with size 0, if you want to be able to return a slice of size 0 and 1 you have to make it a [T].

Thanks, that works! I'm not really sure why you need Box here, and why it doesn't work by returning simply [T]? I'm quite new to Rust

The point is that Rust's arrays are allways of fixed size. So Rust requires you to allways return an array of with one element. That's what the compiler is telling you.
If you realy want some kind of list I recommend using a collection like Vec.
Luckily Option implements the IntoIter-trait. This means you can do what you want by:

fn option_to_list<T>(maybe: Option<T>) -> Vec<T> {
  maybe.into_iter().collect()
}

This will return an empty Vec for None and an Vec of length one for Some(T)

2 Likes

You need the Box because the size of [T] is not known at compile time. But the compiler needs to know the size of everything on the stack, a Box is basically a pointer so its size is known at compile time and it points to something with a size known or not at compile time. This chapter explains what is a Box and how to use it with unsized types.

1 Like

Vec::<> has shared ownership, doesn't it? Depending on the case it might be better to assign a [T] local like:

fn take<T>(ref mut r: [T]) {
    ...
    r = [];
}

fn main() {
    let mut r: [u8] = []
    {
        take(r);
    }
}

Got it. Thanks again for the help!

Vec does not have shared ownership. Also, your example code is invalid: you cannot have variables of type [T].

5 Likes

Oh, I thought [T] would be supported and shared... I feel a bit confused then; btw nor is vector shared in C++.

I was thinking Rust abused of Rcs and these alike under the hood!

Quite the opposite, as a matter of fact: usage of Rc and "alike" is very explicit in Rust.

That's, by the way, why @Aiden01 was confused: most functional languages, since they are garbage collected, use pointers "for everything", hence allowing a [T] in return position (when actually the returned value in those case is something more like GcPtr<[T]>).

Hence the only two solutions to do it in Rust:

  1. Put the slice behind a pointer, with, for instance, just a Box,

  2. Use an enum to be able to "inline" your two possibilities:

    enum MaybeArray<T> {
        NotEmpty([T; 1]),
        Empty,
    }
    

    Which actually just boils down to Option<[T; 1]> (what @farnbams suggested).

Since a fixed-size array such as [T; 1] is not better than just a T, people will go for a tweak of the former solution: "resizable arrays", i.e., a Vector:

/// Rust's `Maybe`
enum Option<T> {
    Some(T),
    None,
}

pub
fn option_to_vec<T> (option: Option<T>) -> Vec<T>
{
    match option {
        Option::Some(value) => vec![value],
        Option::None => vec![],
    }
}

or, with mutation:

pub
fn option_to_vec<T> (option: Option<T>) -> Vec<T>
{
    let mut vec = Vec::new(); // empty mutable vector
    match option {
        Option::Some(value) => {
            vec.push(value);
            vec
        },
        Option::None => vec,
    }
}

at which point the "evaluate to vec" can be factored out:

pub
fn option_to_vec<T> (option: Option<T>) -> Vec<T>
{
    let mut vec = Vec::new(); // empty mutable vector
    match option {
        Option::Some(value) => {
            vec.push(value);
        },
        Option::None => {},
    }
    vec
}

at which point extending the Vec from an option follows the option::IntoIter logic:

pub
fn option_to_vec<T> (option: Option<T>) -> Vec<T>
{
    let mut vec = Vec::new(); // empty mutable vector
    vec.extend(option);
    vec
}

at which point (extending an empty collection), can be one-lined with:

pub
fn option_to_vec<T> (option: Option<T>) -> Vec<T>
{
    <Vec<T> as ::std::iter::FromIterator<T>>::
        from_iter(option)
}

or, in the more succint form (using type inference) (also suggested by @farnbams) :

pub
fn option_to_vec<T> (option: Option<T>) -> Vec<T>
{
    option.into_iter().collect()
}

and at which point, the code can be optimized into using iterators instead of forcing the evaluation (if a caller wants a Vec, all they have to do is call .collect() afterwards):

pub
fn option_elements<T> (option: Option<T>) -> impl Iterator<Item = T>
{
    option.into_iter()
}
4 Likes

There's yet one more possibility – using borrows:

pub fn maybe_to_list<T>(maybe: &Option<T>) -> &[T] {
    match maybe {
       None => &[],
       Some(val) => std::slice::from_ref(val),
    }
}

This won't work for you if you want to operate on owned values, otherwise it's nice because it doesn't allocate new memory like vec.

4 Likes