Joan Vene
Joan Vene

Reputation: 99

Accept both slice and Vec in Rust enum/struct

I have some code similar to this

enum Value<'a> {
    Int(i64),
    Flt(f64),
    Vec(&'a [Value<'a>]),
}

and this lets me reuse some data; however, some times I want to accept heap-allocated data, so I would need something like this

enum Value {
   Int(i64),
   Flt(f64),
   Vec(Box<Vec<Value>>),
}

but now I cannot accept slices! I know I could always have both of them in the same enum, like this

enum Value<'a> {
   Int(i64),
   Flt(f64),
   VecSlice(&'a [Value<'a>]),
   VecBox(Box<Vec<Value<'a>>>),
}

but this is very ugly.

Is there a way to have a struct or enum that accepts both slices and vectors in the same member/variant?

I know that for functions accepting &str and String we can just set the parameters to something like T: Into<String> but I have not figured out how to do something like this for vectors inside data types.

Upvotes: 3

Views: 2405

Answers (2)

Jmb
Jmb

Reputation: 23414

What you want is Cow:

enum Value<'a> {
    Int (i64),
    Flt (f64),
    Vec (Cow<'a, [Value<'a>]>),
}

Unfortunately this doesn't work because of #38962. Until that issue is fixed, you may need to re-implement a specialized version of Cow for Value:

enum MyCow<'a> {
    Borrowed (&'a[Value<'a>]),
    Owned (Vec<Value<'a>>)
}

impl<'a> Deref for MyCow<'a> {
    type Target = [Value<'a>];
    fn deref (&self) -> &[Value<'a>] {
        use crate::MyCow::{ Borrowed, Owned };
        match *self {
            Borrowed (borrowed) => borrowed,
            Owned (ref owned) => &owned,
        }
    }
}

playground

Upvotes: 2

Linear
Linear

Reputation: 22216

I think the closest thing to what you want is the AsRef trait. Notably, Vec<T>, [T], and [T;n] for n <= 32 implement AsRef<[T]>, as do a few other things (like an iterator over a slice). Additionally, Box<T> implements AsRef<T>, but your scenario of Box<Vec<T>> won't quite work here. The gets a little hairy with an enum, though. The type description doesn't quite work as:

enum Value<S>
    where S: AsRef<[Value<S>]>
{
    Int(i64),
    Flt(f64),
    Slice(S),
}

Because you're committed to instantiating exactly one S at a time, and fixing that requires using a Box<dyn S> to make it heterogeneous which gets really messy.

If you can refactor to make this work at the function level or create a higher level type above Value, you can have functions like

fn foo<S>(slice: S) where S: AsRef<[Value]> { }

Fairly easily with this construction, however. In this case if you have a Box<Vec<Value>> the invocation foo(my_vec) won't quite work, but can trivially be fixed with dereferencing since Box<[T]> implements From<Vec<T>>.

use std::convert::AsRef;

enum Value
{
    Int(i64),
    Flt(f64),
}

fn main() {
    use Value::*;

    let x = Box::new(vec![Int(5),Flt(22.5),Int(22)]);
    foo(*x)
}

fn foo<S>(slice: S) where S: AsRef<[Value]> {

}

Playground

Upvotes: 1

Related Questions