因此,对于我正在编写的库,我想计算 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),几乎任何类型都可以实现它,以便与也实现它的其他截然不同的类型进行交互,这可以通过从共同特征中最小化或完全消除泛型的使用来实现。
我们将在下面做出的设计选择是 f64
为Point
生产的普通型出于计算的目的,无论数据的基础类型的内部表示是什么。这应该允许所需的精度。
下面的另一个设计选择是 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/