Rust 中的泛型部分专业化

Edw*_*yan 13 generics rust

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

  • 对于 2D:x,y;
  • 对于 3D:x、y、z;
  • 对于 4D:x、y、z、w;
  • 通用:N 个组件。

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

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;
}
Run Code Online (Sandbox Code Playgroud)

我怎样才能在 Rust 中做同样的事情?

tre*_*tcl 16

你不能像在 C++ 中专门化模板那样在 Rust 中专门化泛型。(Rust 有一个称为“专业化”的功能,但它只适用于impls,并且在这里并不真正相关。)Rust 泛型有时被称为“原则性”,因为它们必须原则上工作(在声明时),而不仅仅是在实践中(一旦实例化)。这是Rust 设计者故意的选择,以避免 C++ 中 SFINAE 的一些更混乱的后果。

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

// 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;
Run Code Online (Sandbox Code Playgroud)

现在,如果您有v: Vector<Fixed<2>>,则可以使用v.0.xor v.0.y,而如果您有 a,Vector<Dynamic>则必须使用v.0[0]and v.0[1]。但是没有办法编写一个使用xand与ory一起使用的通用函数;因为这些s 和s之间没有语义关系,所以这是没有原则的。Vector<Fixed<2>>Vector<Fixed<3>>xy

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

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> {}
Run Code Online (Sandbox Code Playgroud)

现在您可以编写适用于Vector<N>和 使用x和的通用函数y,但要对其进行调整以允许突变并不那么容易。一种方法是添加x_mut,y_mutz_mut返回的方法&mut f32

相关问题