这是我的代码的简化示例:
#[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[..]);
答案 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
并已针对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
轻松添加它:< / 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(),
}
}
}