Rust中的泛型部分专业化 [英] Generics partial specialization in Rust

查看:46
本文介绍了Rust中的泛型部分专业化的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

让我们看一些数学向量的例子.根据空间尺寸,它由不同数量的组件组成.

  • 对于2D:x,y;
  • 对于3D:x,y,z;
  • 对于4D:x,y,z,w;
  • 通用:N个成分.

在C ++中,我可以使用SFINAE概念来实现它.

 模板< size_t D,类型名T,类型名= void>struct Vector;//实现2Dtemplate< size_t D,类型名称T>结构向量< D,T,std :: enable_if_t<(D == 2)>{T x;Ťÿ;}//实现3Dtemplate< size_t D,类型名称T>结构向量< D,T,std :: enable_if_t<(D == 3)>{T x;Ťÿ;z}//实现4Dtemplate< size_t D,类型名称T>结构向量< D,T,std :: enable_if_t<(D == 4)>{T x;Ťÿ;zT w;} 

如何在Rust中做同样的事情?

解决方案

您不能像在C ++中那样专门化Rust中的泛型.(Rust具有称为特殊化"的功能,但仅适用于 impl ,在这里并不真正相关.)Rust泛型有时被称为原理化".因为他们必须原则(根据声明)工作,而不仅仅是实践(一旦实例化).对于Rust的设计师来说,这是一个刻意的选择,以避免C ++中SFINAE带来的更混乱的后果.

我可以想到两种主要方法来实现与Rust中的C ++代码相似的效果,具体取决于代码的通用上下文.一种方法是使用特征作为类型级别的函数来计算参数化结构的内容类型,该内容类似于C ++版本,但具有更详细的字段访问权限(为简单起见,我想像 T 在这些示例中为 f32 ):

 //包含实际数据的类型struct Vector2 {x:f32,y:f32,}struct Vector3 {x:f32,y:f32,z:f32,}//将用于参数化类型构造函数的类型struct Fixed< const N:usize> ;;结构动态//一个类型级别的函数,该函数说明哪种数据对应于哪种类型trait VectorSize {输入数据;}固定2的impl VectorSize.{类型数据= Vector2;}固定3的impl VectorSize.{类型数据= Vector3;}动态{类型Data = Vec< f32> ;;}//将所有内容拉在一起结构向量Z(Z :: Data)其中Z:VectorSize; 

现在,如果您拥有 v:Vector< Fixed< 2>> ,则可以使用 v.0.x v.0.y ,而如果您有 Vector< Dynamic> ,则必须使用 v.0 [0] v.0 [1] .但是没有办法编写使用 x y 的通用函数,该函数将与 Vector< Fixed< 2>> Vector< Fixed< 3>> ;因为在这些 x s和 y s之间没有语义关系,所以这是无原则的.

另一种选择是将数组放入 Vector 中,并使用 x y 便捷方法访问元素0和1:

  struct Vector< const N:usize>{xs:[f32;N],}impl< const N:usize>向量N{fn x(& self)->f32,其中Self:SizeAtLeast 2{self.xs [0]}fn y(& self)->f32,其中Self:SizeAtLeast 2{self.xs [1]}fn z(& self)->f32:Self:SizeAtLeast 3;{self.xs [2]}}//在当前的Rust中,您无法绑定const泛型的属性,因此//做类似的事情,在其中为每个相关问题实现特质//数字.宏可以减少繁琐的工作.将来您应该能够//只需将`N`的边界添加到`x`,`y`和`z`.特质SizeAtLeast< const N:usize>{}impl SizeAtLeast 2用于向量2的{}impl SizeAtLeast 2用于向量3的{}impl SizeAtLeast 2用于向量4的{}impl SizeAtLeast 3用于向量3的{}impl SizeAtLeast 3用于向量4的{} 

现在您可以编写适用于 Vector< N> 的通用函数,并使用 x y ,但是适应起来并不容易这允许突变.一种方法是添加返回& mut f32 x_mut y_mut z_mut 方法./p>

相关问题

Let's look at some example of mathematics vector. It consists of a different number of components depending on the space dimension.

  • For 2D: x, y;
  • For 3D: x, y, z;
  • For 4D: x, y, z, w;
  • Generic: N components.

In C++ I can use SFINAE concept to implement it.

template <size_t D, typename T, typename = void>
struct Vector;

// Implement for 2D
template<size_t D, typename T>
struct Vector <D, T, std::enable_if_t<(D == 2)>>
{
    T x;
    T y;
}

// Implement for 3D
template<size_t D, typename T>
struct Vector <D, T, std::enable_if_t<(D == 3)>>
{
    T x;
    T y;
    T z;
}
    
// Implement for 4D
template<size_t D, typename T>
struct Vector <D, T, std::enable_if_t<(D == 4)>>
{
    T x;
    T y;
    T z;
    T w;
}

How can I do the same in Rust?

解决方案

You cannot specialize generics in Rust like specializing templates in C++. (Rust has a feature called "specialization", but it only applies to impls, and it's not really relevant here.) Rust generics are sometimes called "principled" because they have to work in principle (upon declaration), not just in practice (once instantiated). This is a deliberate choice on the part of Rust's designers to avoid some of the messier consequences of SFINAE in C++.

I can think of two main ways to achieve a similar effect to your C++ code in Rust, depending on the generic context of the code. One way is to use a trait as a type level function to compute the content type of a parameterized struct, which is similar to the C++ version but has slightly more verbose field access (for simplicity, I'll imagine that T is f32 for these examples):

// types that contain the actual data
struct Vector2 {
    x: f32,
    y: f32,
}

struct Vector3 {
    x: f32,
    y: f32,
    z: f32,
}

// types that will be used to parameterize a type constructor
struct Fixed<const N: usize>;
struct Dynamic;

// a type level function that says what kind of data corresponds to what type
trait VectorSize {
    type Data;
}

impl VectorSize for Fixed<2> {
    type Data = Vector2;
}

impl VectorSize for Fixed<3> {
    type Data = Vector3;
}

impl VectorSize for Dynamic {
    type Data = Vec<f32>;
}

// pulling it all together
struct Vector<Z>(Z::Data) where Z: VectorSize;

Now, if you have v: Vector<Fixed<2>> you can use v.0.x or v.0.y, whereas if you have a Vector<Dynamic> you have to use v.0[0] and v.0[1]. But there's no way to write a generic function that uses x and y that will work with either Vector<Fixed<2>> or Vector<Fixed<3>>; since there's no semantic relationship between those xs and ys, that would be unprincipled.

Another option would be putting an array in Vector and making x and y convenience methods that access elements 0 and 1:

struct Vector<const N: usize> {
    xs: [f32; N],
}

impl<const N: usize> Vector<N> {
    fn x(&self) -> f32 where Self: SizeAtLeast<2> {
        self.xs[0]
    }

    fn y(&self) -> f32 where Self: SizeAtLeast<2> {
        self.xs[1]
    }
    
    fn z(&self) -> f32 where Self: SizeAtLeast<3> {
        self.xs[2]
    }
}

// In current Rust, you can't bound on properties of const generics, so you have
// to do something like this where you implement the trait for every relevant
// number. Macros can make this less tedious. In the future you should be able to
// simply add bounds on `N` to `x`, `y` and `z`.
trait SizeAtLeast<const N: usize> {}

impl SizeAtLeast<2> for Vector<2> {}
impl SizeAtLeast<2> for Vector<3> {}
impl SizeAtLeast<2> for Vector<4> {}

impl SizeAtLeast<3> for Vector<3> {}
impl SizeAtLeast<3> for Vector<4> {}

Now you can write generic functions that work for Vector<N> and use x and y, but it's not as easy to adapt this to allow mutation. One way to do so is to add x_mut, y_mut and z_mut methods that return &mut f32.

Related question

这篇关于Rust中的泛型部分专业化的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆