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

问题描述 投票:2回答:5

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

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

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

现在我的主要问题。我可以定义传递切片的新DataVars,例如如下:

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

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

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

编辑:

现在我尝试使用特征对象而不是枚举实现相同的代码,但结果更糟糕......是不是真的有任何解决方案?

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[..]);
rust
5个回答
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返回的引用的生命周期受传递给selfas_ref参数的生命周期的约束。如果我们将参数更改回data: T,那么data.as_ref()现在隐式引用data以调用as_ref,它需要共享引用self&self)。但是这里的data是一个局部参数,这意味着这个隐式引用操作创建的引用的生命周期仅限于本地函数,data.as_ref()返回的引用也是如此。这个寿命比'a短,所以我们不能将它存储在DataVar并返回它。

如果您需要处理除引用值之外的非引用的data值,则不幸的是,此解决方案无法支持该值。


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的提示!


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

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

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

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(),
        }
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.