如何修改我的构造函数以接受切片或对数组或向量的引用

时间:2019-02-10 23:06:36

标签: rust

这是我的代码的简化示例:

#[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(),
        }
    }
}

首先,考虑到我需要将不同的DataVar强制转换为同一向量,并且我想避免使用特征对象,您认为我的实现是正确的还是有改进的建议?

现在是我的主要问题。我可以定义通过切片的新DataVar,例如:

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

如何修改我的构造函数,使其不仅适用于切片,而且还可以引用数组或向量?例如,我希望以下内容也能正常工作:

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

编辑:

现在,我尝试使用trait对象而不是枚举来实现相同的代码,但是结果甚至更糟……真的没有解决方案吗?

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[..]);

5 个答案:

答案 0 :(得分:2)

我们可以使用AsRef特性将对数组的引用转换为向量,或者将矢量转换为切片。 AsRef是一个通用特征,因此我们需要引入第二个类型参数来表示“中间类型”(切片类型)。调用as_ref之后,我们得到了一个切片,可以使用Data将其转换为into

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

但是请注意,data参数现在是一个引用:这是必需的,因为as_ref返回的引用的生存期受传递到{的self参数的生存期的约束。 {1}}。如果我们将参数改回as_ref,则data: T现在隐式引用data.as_ref()以便调用data,它期望对as_ref({ {1}})。但是self是一个局部参数,这意味着通过此隐式引用操作创建的引用的生存期仅限于局部函数,&self返回的引用也是如此。此生存期短于data,因此我们无法将其存储在data.as_ref()中并返回。

不幸的是,如果除了引用值之外,还需要处理'a个非引用值,那么此解决方案将无法支持该值。

答案 1 :(得分:1)

这实际上是针对我的情况的最佳解决方案:

impl<'a> DataVar<'a> {
    fn new<T, U>(name: &'a str, data: &'a T) -> Self
    where
        T: AsRef<[U]> + ?Sized,
        U: 'a,
        &'a [U]: Into<Data<'a>>,
    {
        Self {
            name,
            data: data.as_ref().into(),
        }
    }
}

它与切片,对向量的引用以及对长度达到32的数组的引用(实现AsRef<[T]> https://doc.rust-lang.org/beta/std/convert/trait.AsRef.html

)一起使用

感谢@Francis的提示!

答案 2 :(得分:1)

在我看来,AsRef似乎不是正确的抽象,原因有两个:首先,因为一种类型可能同时实现AsRef<[i32]>AsRef<[f64]> ,目前尚不清楚在这种情况下应如何处理;其次,因为已经有内置的语言功能(coercion)可以将Vec<T>&[T; n]转换为&[T],而您没有利用它。

我想要编写一个基本上像这样的new函数:

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

如果我们可以告诉编译器如何处理&[T; n],它将自动与&Vec<T>&Cow<T>T等一起工作。有意义的是,您可以做出一个特征,该特征知道如何将&'a [Self]转换为Data并已针对i32f64实现,所以我们可以这样做:

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)
    }
}

new上绑定的特征变得微不足道:

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

我发现它比带有FromAsRef的版本更具可读性,但是如果您仍然想要From,则可以使用通用impl轻松添加它:< / p>

impl<'a, T> From<&'a [T]> for Data<'a>
where
    T: Item,
{
    fn from(v: &'a [T]) -> Self {
        T::into_data(v)
    }
}

答案 3 :(得分:1)

实际上,这是恕我直言的最佳解决方案……与我的初始代码类似,我只需要在new构造函数中进行一个小修复:

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

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

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

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

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

答案 4 :(得分:0)

@trentcl您的解决方案很棒!现在,我了解了如何利用胁迫。

但是我做了如下调整,除非您发现其中的任何缺陷,否则我将最终使用此代码,谢谢!

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

trait IntoData<'a>: Sized {
    fn into_data(&self) -> Data<'a>;
}

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

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

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

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