Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to modify my constructor in order to accept either a slice or a reference to array or vector

Tags:

rust

This is a simplified example of my code:

#[derive(Debug, Clone, Copy)]
enum Data<'a> {
    I32(&'a [i32]),
    F64(&'a [f64]),
}

impl<'a> From<&'a [i32]> for Data<'a> {
    fn from(v: &'a [i32]) -> Data<'a> {
        Data::I32(v)
    }
}

impl<'a> From<&'a [f64]> for Data<'a> {
    fn from(v: &'a [f64]) -> Data<'a> {
        Data::F64(v)
    }
}

#[derive(Debug, Clone, Copy)]
struct DataVar<'a> {
    name: &'a str,
    data: Data<'a>,
}

impl<'a> DataVar<'a> {
    fn new<T>(name: &'a str, data: T) -> Self
    where
        T: Into<Data<'a>>,
    {
        Self {
            name,
            data: data.into(),
        }
    }
}

First of all, considering that I need to cast different DataVars to the same vector, and I would like to avoid using trait objects, do you think my implementation is correct or do you have suggestions for improvement?

Now my main question. I can define new DataVars passing a slice, for instance as follows:

let x = [1, 2, 3];
let xvar = DataVar::new("x", &x[..]);

How can I modify my constructor so that it works not only with a slice, but also with a reference to array or vector? For instance I would like the following to work as well:

let x = [1, 2, 3];
let xvar = DataVar::new("x", &x);

EDIT:

Now I tried implementing the same code using a trait object instead of an enum, but the result is even worse... isn't there really any solution to this?

trait Data: std::fmt::Debug {}

impl Data for &[i32] {}

impl Data for &[f64] {}

#[derive(Debug, Clone, Copy)]
struct DataVar<'a> {
    name: &'a str,
    data: &'a dyn Data,
}

impl<'a> DataVar<'a> {
    fn new<T>(name: &'a str, data: &'a T) -> Self
    where
        T: Data,
    {
        Self { name, data }
    }
}

let x = [1, 2, 3];
let xvar = DataVar::new("x", &&x[..]);
like image 207
lucatrv Avatar asked Oct 16 '25 20:10

lucatrv


1 Answers

To me, AsRef doesn't seem to be the right abstraction for two reasons: first, because it's possible (if unlikely) for a type to implement both AsRef<[i32]> and AsRef<[f64]>, and it's not clear what should happen in that case; and second, because there's already a built-in language feature (coercion) that can turn Vec<T> or &[T; n] into &[T], and you're not taking advantage of it.

What I'd like is to write a new function that looks basically like this:

    fn new<T>(name: &'a str, data: &'a [T]) -> Self
    where
        // what goes here?

This will automatically work with &[T; n], &Vec<T>, &Cow<T>, etc. if we can tell the compiler what to do with T. It makes sense that you could make a trait that knows how to convert &'a [Self] to Data and is implemented for i32 and f64, so let's do that:

trait Item: Sized {
    fn into_data<'a>(v: &'a [Self]) -> Data<'a>;
}

impl Item for i32 {
    fn into_data<'a>(v: &'a [i32]) -> Data<'a> {
        Data::I32(v)
    }
}

impl Item for f64 {
    fn into_data<'a>(v: &'a [f64]) -> Data<'a> {
        Data::F64(v)
    }
}

The trait bound on new becomes trivial:

impl<'a> DataVar<'a> {
    fn new<T>(name: &'a str, data: &'a [T]) -> Self
    where
        T: Item,
    {
        Self {
            name,
            data: T::into_data(data),
        }
    }
}

I find this more readable than the version with From and AsRef, but if you still want From, you can easily add it with a generic impl:

impl<'a, T> From<&'a [T]> for Data<'a>
where
    T: Item,
{
    fn from(v: &'a [T]) -> Self {
        T::into_data(v)
    }
}
like image 166
trent Avatar answered Oct 18 '25 14:10

trent



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!