rust - 在 Rust nightly 中使用关联的 const 和 const 泛型时类型不匹配

标签 rust const-generics associated-const

因此,对于我正在编写的库,我想计算 N 维(2、3、4 等)中两点之间的距离,并且我有一个 Point 特征,以便库的用户可以使用它只要它们是“点状”的,就可以在它们自己的类型上发挥作用。

我有一个特征“Point”,它具有维度 (N) 和浮点类型 (T) 作为关联类型和常量:

pub trait Point: Copy + Clone {
    type T: num::Float;
    const N: usize;

    fn as_array(&self) -> [Self::T; Self::N];
}

以及使用它的函数:

fn dist<P>(a: &P, b: &P) -> P::T
where
    P: Point,
    [(); P::N]: ,
{
    // implementation goes here ...
}

按预期使用 Point 特征的示例:

#[derive(Copy, Clone)]
struct MyCustomPoint { a: f64, b: f64 }

impl Point for MyCustomPoint {
    type T = f64;
    const N: usize = 2;

    fn as_array(&self) -> [Self::T; Self::N] {
        [self.a, self.b]
    }
}

问题

如果我实现 [f32;N] 的 Point 特征,我会遇到以下问题:

error[E0308]: mismatched types
  --> src\main.rs:63:9
   |
63 |         *self
   |         ^^^^^ expected `Self::N`, found `N`
   |
   = note: expected type `Self::N`
              found type `N`

导致问题的代码:

impl<const N: usize> Point for [f32; N] {
    type T = f32;
    const N: usize = N;

    fn as_array(&self) -> [Self::T; Self::N] {
        *self
    }
}

当在代码中使用数字工作正常时,为什么下面的代码会导致类型不匹配错误?

impl Point for [f32; 3] {
    type T = f32;
    const N: usize = 3;

    fn as_array(&self) -> [Self::T; Self::N] {
        *self
    }
}

所有代码放在一个 block 中:

#![feature(adt_const_params)]
#![feature(generic_const_exprs)]

use num::Float;

pub trait Point: Copy + Clone {
    type T: num::Float;
    const N: usize;

    fn as_array(&self) -> [Self::T; Self::N];
}

fn dist<P>(a: &P, b: &P) -> P::T
where
    P: Point,
    [(); P::N]: ,
{
    let mut dist_sq: P::T = num::zero();
    for i in 0..P::N {
        let delta = (a.as_array())[i] - (b.as_array())[i];
        dist_sq = dist_sq + delta * delta;
    }
    dist_sq.sqrt()
}

// Works
#[derive(Copy, Clone)]
struct MyCustomPoint { a: f64, b: f64 }

impl Point for MyCustomPoint {
    type T = f64;
    const N: usize = 2;

    fn as_array(&self) -> [Self::T; Self::N] {
        [self.a, self.b]
    }
}

// Works
// impl Point for [f32; 3] {
//     type T = f32;
//     const N: usize = 3;

//     fn as_array(&self) -> [Self::T; Self::N] {
//         *self
//     }
// }

// Doesn't work
impl<const N: usize> Point for [f32; N] {
    type T = f32;
    const N: usize = N;

    fn as_array(&self) -> [Self::T; Self::N] {
        *self
    }
}

fn main() {
    let a = [0f32, 1f32, 0f32];
    let b = [3f32, 1f32, 4f32];
    assert_eq!(dist(&a, &b), 5f32);
}

此外,我发现以下是一种解决方法。

fn as_array(&self) -> [Self::T; Self::N] {
    // *self

    // Workaround - replace with something simpler if possible.
    let mut array = [0f64; Self::N];
    array[..Self::N].copy_from_slice(&self[..Self::N]);
    array
}

最佳答案

仅使用稳定的 Rust,我相信你可以实现你想要的。假设你想要的效果只是能够调用dist()任意(但相同)维度的数组,或具有相同通用参数值并从 .as_array() 返回相同大小数组的 Point 对象.

添加.as_array()所有浮点值数组的方法,遵循声明特征以追溯向现有类型添加功能的常见模式。 在示例中使用数组作为实现者有点奇怪 - 所有这些工作都是为了返回其自身的副本;但这种方法也应该适用于其他更有意义的类型。

use num::Float;

pub trait Point<T: Float, const N: usize>: Copy + Clone {
    fn as_array(&self) -> [T; N];
}

impl<T: Float, const N: usize> Point<T, N> for [T; N] {
    fn as_array(&self) -> [T; N] {
        *self
    }
}

然后是一个函数,它采用正确的泛型组合,以便编译器根据其参数进行定制。

fn dist<P, T, const N: usize>(a: &P, b: &P) -> T
where
    P: Point<T, N>,
    T: Float,
{
    let mut dist_sq: T = num::zero();
    for i in 0..N {
        let delta = (a.as_array())[i] - (b.as_array())[i];
        dist_sq = dist_sq + delta * delta;
    }
    dist_sq.sqrt()
}

这使得代码如下:

fn main() {
    let a = [0f32, 1f32, 0f32];
    let b = [3f32, 1f32, 4f32];
    assert_eq!(dist(&a, &b), 5f32);
    println!("dist(&a, &b): {}", dist(&a, &b));
    
    let c = [1f64, 2f64, 3f64, 4f64,  5f64];
    let d = [6f64, 7f64, 8f64, 9f64, 10f64];
    
    println!("dist(&c, &d): {}", dist(&c, &d));
}

const 泛型类型 ( trait Foo<const A: Float> ) 与 const 关联类型 ( trait Foo { const A: Float; } ) 具有不同的用途。常量泛型使特征更加灵活,而关联类型使特征更加受限和具体。因此,从泛型参数 ( trait Foo<const A: Float> { const A: Float = A; ) 分配关联类型会混合两个具有相反意图的功能,并且没有多大意义。

// Doesn't work
impl<const N: usize> Point for [f32; N] {
    type T = f32;
    const N: usize = N;

    fn as_array(&self) -> [Self::T; Self::N] {
        *self
    }
}

如果您想要一个真正的多态接口(interface),几乎任何类型都可以实现它,以便与也实现它的其他截然不同的类型进行交互,这可以通过从共同特征中最小化或完全消除泛型的使用来实现。

我们将在下面做出的设计选择是 f64Point生产的普通型出于计算的目的,无论数据的基础类型的内部表示是什么。这应该允许所需的精度。

下面的另一个设计选择是 Point s 以通用格式 ( f64 ) 返回其坐标值的迭代器。这将 Point 的内部表示解耦。来自接口(interface)的数据并允许 Point以任何最有效的方式实现迭代器的自由,无论是在性能还是人体工程学方面。这样,点对象不必创建特定大小的数组、填充它并将其返回到堆栈上。

use rand::random;
use num::Float;

// A trait that avoids generics and assoc types providing a polymorphic dynamic
// interface.
//
trait Point {
    fn distance(&self, other: &dyn Point) -> f64 
    {
        self.coords().zip(other.coords())
            .fold(0.0, |acc, (a, b)| acc + (a - b) * (a - b))
            .sqrt()
    }
    fn coords(&self) -> Box<dyn Iterator<Item=f64>>;
}

// Implemented for all float arrays.
//
impl<T: 'static + Float + Into<f64>, const N: usize> Point for [T; N] 
{
    fn coords(&self) -> Box<dyn Iterator<Item=f64>> 
    {
        Box::new(self.clone().into_iter().map(|n| n.into()))
    }
}

// Another point object type.
//
struct RandomPoint;

impl Point for RandomPoint {
    fn coords(&self) -> Box<dyn Iterator<Item=f64>>
    {
        Box::new((0..10).map(|_| random::<f64>()))
    }
}

fn main() {
    let a = [0f32, 1f32, 0f32];
    let b = [3f32, 1f32, 4f32];
    println!("a.distance(&b): {}", a.distance(&b));
    
    let c = [0f64, 1.,  2.,  3.,  4.,  5.,  6.,  7.];
    let d = [8f64, 9., 10., 11., 12., 13., 14., 16.];
    println!("c.distance(&d): {}", c.distance(&d));
    
    let r = RandomPoint;

    // We can mix and match the parameters to `.distance()`.
    //
    println!("a.distance(&r): {}", a.distance(&r));  // [f32] & RandomPoint
    println!("a.distance(&d): {}", a.distance(&d));  // [f32] & [f64]
}

关于rust - 在 Rust nightly 中使用关联的 const 和 const 泛型时类型不匹配,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/69719279/

相关文章:

rust - 如何从 std::io::Error 获取消息字符串?

reference - 有没有办法将引用类型克隆到拥有的类型中?

rust - 一次实现多种类型的特征

rust - Rust 是否支持具有运行时确定值的常量泛型类型?

generics - 满足与 const 泛型表达式绑定(bind)的特征,这可能吗?

generics - 如何初始化常量泛型数组?

types - 为什么类型别名不能使用 Rust 中原始类型的关联常量?

rust - `pub(self)` 可见性与没有 `pub` 属性有何不同?