#rust #const-generics #associated-const
Вопрос:
Итак, для библиотеки, которую я пишу, я хочу вычислить расстояние между двумя точками в N измерениях (2, 3, 4 и т. Д.), И у меня есть точечный признак, чтобы пользователи библиотеки могли использовать эту функцию для своих собственных типов, если они «точечные».
У меня есть признак «Point», размеры которого (N) и тип с плавающей запятой (T) сохраняются как связанный тип и константа:
pub trait Point: Copy Clone {
type T: num::Float;
const N: usize;
fn as_array(amp;self) -> [Self::T; Self::N];
}
и функция, которая его использует:
fn dist<P>(a: amp;P, b: amp;P) -> P::T
where
P: Point,
[(); P::N]: ,
{
// implementation goes here ...
}
Пример использования точечного признака по назначению:
#[derive(Copy, Clone)]
struct MyCustomPoint { a: f64, b: f64 }
impl Point for MyCustomPoint {
type T = f64;
const N: usize = 2;
fn as_array(amp;self) -> [Self::T; Self::N] {
[self.a, self.b]
}
}
Проблема
Если я реализую точечный признак для [f32;N], я получаю следующую проблему:
error[E0308]: mismatched types
--> srcmain.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(amp;self) -> [Self::T; Self::N] {
*self
}
}
Почему приведенный ниже код вызывает ошибку несоответствующих типов, когда использование числа в коде работает нормально?
impl Point for [f32; 3] {
type T = f32;
const N: usize = 3;
fn as_array(amp;self) -> [Self::T; Self::N] {
*self
}
}
Весь код, собранный в одном блоке:
#![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(amp;self) -> [Self::T; Self::N];
}
fn dist<P>(a: amp;P, b: amp;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(amp;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(amp;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(amp;self) -> [Self::T; Self::N] {
*self
}
}
fn main() {
let a = [0f32, 1f32, 0f32];
let b = [3f32, 1f32, 4f32];
assert_eq!(dist(amp;a, amp;b), 5f32);
}
Кроме того, я обнаружил, что следующее является обходным решением.
fn as_array(amp;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(amp;self[..Self::N]);
array
}
Комментарии:
1. Const дженерики все еще находятся в разработке и имеют много нерешенных проблем: github.com/rust-lang/rust/labels/A-const-generics . Я бы предположил, что это один из них.
2. Я надеялся, что проблема во мне, а не в компиляторе, ха-ха. Спасибо за ссылку, я собираюсь посмотреть, сообщается ли об этой конкретной проблеме.
3. Я не думаю, что проблема заключается в новых функциях, а в применении существующих стабильных функций дженериков. Работа с некоторыми незначительными изменениями: ссылка на игровую площадку
4. Я остановился на вашем решении, Тодд, оно кажется лучшим для моих целей. В моем проекте используется связанная константа в другом месте, которую я здесь не показал, но это кажется больше проблем, чем того стоит. Спасибо
5. Рад, что я мог бы помочь @scott97, всегда пожалуйста.
Ответ №1:
Я полагаю, что вы можете достичь того, чего хотите, используя только стабильный Rust. Предполагая, что эффект, который вы хотите, — это просто иметь возможность вызывать dist()
массивы произвольных (но одинаковых) размеров или точечных объектов, которые имеют одинаковые значения общих параметров и возвращают массив одинакового размера .as_array()
.
Добавление .as_array()
метода ко всем массивам значений с плавающей запятой следует общему шаблону объявления признака для ретроактивно добавления функций к существующим типам. Использование массива в качестве разработчика в примере немного странно — вся эта работа возвращает копию самого себя; но этот подход должен применяться и к другим типам, которые имеют больше смысла.
use num::Float;
pub trait Point<T: Float, const N: usize>: Copy Clone {
fn as_array(amp;self) -> [T; N];
}
impl<T: Float, const N: usize> Point<T, N> for [T; N] {
fn as_array(amp;self) -> [T; N] {
*self
}
}
Затем функция, которая использует правильную комбинацию обобщений, чтобы компилятор адаптировал ее к своим параметрам.
fn dist<P, T, const N: usize>(a: amp;P, b: amp;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(amp;a, amp;b), 5f32);
println!("dist(amp;a, amp;b): {}", dist(amp;a, amp;b));
let c = [1f64, 2f64, 3f64, 4f64, 5f64];
let d = [6f64, 7f64, 8f64, 9f64, 10f64];
println!("dist(amp;c, amp;d): {}", dist(amp;c, amp;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(amp;self) -> [Self::T; Self::N] {
*self
}
}
Если вам нужен действительно полиморфный интерфейс, который может реализовать практически любой тип для взаимодействия с другими совершенно разными типами, которые также его реализуют, этого можно достичь, минимизировав или полностью исключив использование дженериков из общего признака.
Выбор дизайна, который мы сделаем ниже, будет заключаться в том, чтобы f64
использовать в качестве общего типа, создаваемого Point
для целей вычислений, независимо от того, каковы внутренние представления данных базовых типов. Это должно обеспечить необходимую точность.
Другой вариант дизайна, приведенный ниже, заключается в том, чтобы Point
s возвращали итератору свои значения координат в общем формате ( f64
). Это отделяет внутреннее представление Point
данных от интерфейса и позволяет a Point
свободно реализовывать свой итератор любым наиболее эффективным способом, будь то с точки зрения производительности или эргономики. Таким образом, объекту point не нужно создавать массив определенного размера, заполнять его и возвращать в стек.
use rand::random;
use num::Float;
// A trait that avoids generics and assoc types providing a polymorphic dynamic
// interface.
//
trait Point {
fn distance(amp;self, other: amp;dyn Point) -> f64
{
self.coords().zip(other.coords())
.fold(0.0, |acc, (a, b)| acc (a - b) * (a - b))
.sqrt()
}
fn coords(amp;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(amp;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(amp;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(amp;b): {}", a.distance(amp;b));
let c = [0f64, 1., 2., 3., 4., 5., 6., 7.];
let d = [8f64, 9., 10., 11., 12., 13., 14., 16.];
println!("c.distance(amp;d): {}", c.distance(amp;d));
let r = RandomPoint;
// We can mix and match the parameters to `.distance()`.
//
println!("a.distance(amp;r): {}", a.distance(amp;r)); // [f32] amp; RandomPoint
println!("a.distance(amp;d): {}", a.distance(amp;d)); // [f32] amp; [f64]
}