I am trying to write a data-structure such that it is generic over different dimensionality.
The beginning is:
/// A field of adjacent sections in a given dimensionality.
/// With 1, you have `|left|center|right|`.
/// With 2 dimensions you have:
/// ```
/// up_left│ up│up_right
/// ─────────┼──────┼──────────
/// left│center│right
/// ─────────┼──────┼──────────
/// down_left│ down│down_right
/// ```
/// Etc.
#[derive(Debug)]
struct Adjacents<T, const D: u32>([T; size(D)])
const fn size(d: u32) -> usize { 3u32.pow(d) as usize }
When trying to write implementations like:
impl<T> Adjacents<T,1u32> { /*...*/ }
I'm encountering the severe warning (in a few instances):
warning: cannot use constants which depend on generic parameters in types
--> src/main.rs:26:9
|
26 | impl<T> Adjacents<T,1u32> {
| ^^^^^^^^^^^^^^^^^
|
= note: `#[warn(const_evaluatable_unchecked)]` on by default
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
= note: for more information, see issue #76200 <https://github.com/rust-lang/rust/issues/76200>
In my use case certain functionality can be generic across the stored data type (T) but not generic across dimensionality (D), so it seems only inevitable I must use Adjacents<T,1u32> throughout.
Not to mention D does not depend on T (how could I convey this to the compiler?)
What am I missing here?
Full code here:
#![feature(generic_const_exprs)]
use std::ops::Index;
const fn size(d: u32) -> usize {
3u32.pow(d) as usize
}
/// A field of adjacent sections in a given dimensionality.
/// With 1, you have `|left|center|right|`.
/// With 2 dimensions you have:
/// ```
/// up_left│ up│up_right
/// ─────────┼──────┼──────────
/// left│center│right
/// ─────────┼──────┼──────────
/// down_left│ down│down_right
/// ```
/// Etc.
#[derive(Debug)]
struct Adjacents<T, const D: u32>([T; size(D)])
where
[(); size(D)]:;
#[allow(unused)]
impl<T> Adjacents<T, 1u32> {
pub const LEFT: usize = 0;
pub const CENTER: usize = 1;
pub const RIGHT: usize = 2;
}
#[allow(unused)]
mod d1 {
pub const LEFT: usize = 0;
pub const CENTER: usize = 1;
pub const RIGHT: usize = 2;
}
impl<T, const D: u32> Index<usize> for Adjacents<T, D>
where
[(); size(D)]:,
{
type Output = T;
fn index(&self, index: usize) -> &Self::Output {
self.0.index(index)
}
}
/// A trait for functionality which can exist in many different dimensions, but requires explicit implementation for each dimension.
pub trait Border<const D: u32> {
fn border(x: usize) -> Self;
}
/// We want to form a structure where the center is full and the borders are only full where they are adjacent to the center.
/// In 1d like:
/// ```
/// // [[None,None,Some(T)],[Some(T),Some(T),Some(T)],[Some(T),None,None]]
/// ```
/// (by using a trait and impl this allows us to do this for given any depth)
impl<T: Border<1u32>> Border<1u32> for Adjacents<Option<T>, 1u32> {
fn border(x: usize) -> Self {
Self(match x {
d1::LEFT => [None, None, Some(T::border(d1::RIGHT))],
d1::CENTER => [
Some(T::border(d1::LEFT)),
Some(T::border(d1::CENTER)),
Some(T::border(d1::RIGHT)),
],
d1::RIGHT => [Some(T::border(d1::LEFT)), None, None],
_ => unreachable!(),
})
}
}
#[derive(Debug)]
struct Tp(usize);
impl Border<1u32> for Tp {
fn border(x: usize) -> Self {
Self(x)
}
}
fn main() {
let field: Adjacents<_, 1> = Adjacents([1u32, 2u32, 3u32]);
println!("left: {}", field[d1::LEFT]);
// [[None,None,Some(Tp)],[Some(T),Some(T),Some(T)],[Some(T),None,None]]
let border: Adjacents<Option<Adjacents<Option<Tp>, 1u32>>, 1u32> =
Adjacents::border(d1::CENTER);
println!("{:?}", border);
}