这是我的代码的简化示例:
#[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
s转换为相同的向量,并且我想避免使用特征对象,您认为我的实现是正确的还是您有改进建议?
现在我的主要问题。我可以定义传递切片的新DataVar
s,例如如下:
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[..]);
我们可以使用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
的as_ref
参数的生命周期的约束。如果我们将参数更改回data: T
,那么data.as_ref()
现在隐式引用data
以调用as_ref
,它需要共享引用self
(&self
)。但是这里的data
是一个局部参数,这意味着这个隐式引用操作创建的引用的生命周期仅限于本地函数,data.as_ref()
返回的引用也是如此。这个寿命比'a
短,所以我们不能将它存储在DataVar
并返回它。
如果您需要处理除引用值之外的非引用的data
值,则不幸的是,此解决方案无法支持该值。
这实际上是我案例的最佳解决方案:
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的提示!
对我来说,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
并且为i32
和f64
实现的特性,所以让我们这样做:
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),
}
}
}
我觉得这比使用From
和AsRef
的版本更具可读性,但是如果你仍然需要From
,你可以轻松地添加一个通用的impl
:
impl<'a, T> From<&'a [T]> for Data<'a>
where
T: Item,
{
fn from(v: &'a [T]) -> Self {
T::into_data(v)
}
}
实际上,这是恕我直言最好的解决方案......所以类似于我的初始代码,我只需要在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(),
}
}
}
@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(),
}
}
}