Accessing associated contant in field declaration of generic struct

52 Views Asked by At

I have a tag trait that has a associated constant:

trait Trait {
    const N: usize;
}

I have a type that is generic, bounded by the trait. I'd like to be able to use N in the declaration:

struct S<T: Trait> {
    field: [f64; T::N],
}

My expectation is that for any implementation of Trait that specifies N, I can have an S with an array of that many floats in it. Instead, I get the following compiler error:

error: generic parameters may not be used in const operations
 --> src/main.rs:8:18
  |
8 |     field: [f64; T::N],
  |                  ^^^^ cannot perform const operation using `T`
  |
  = note: type parameters may not be used in const expressions

error[E0392]: parameter `T` is never used
 --> src/main.rs:7:10
  |
7 | struct S<T: Trait> {
  |          ^ unused parameter
  |
  = help: consider removing `T`, referring to it in a field, or using a marker such as `PhantomData`

For more information about this error, try `rustc --explain E0392`.

While I understand the error itself, I don't understand the reason for it or how to get around it.

3

There are 3 best solutions below

4
Taylor Allred On BEST ANSWER

The trait needs an impl to define that const N.

Try this:

trait MyTrait {
    const NN: usize;
}

struct MyStruct {
    abc: [f64; <MyStruct as MyTrait>::NN],
}

impl MyTrait for MyStruct {
    const NN: usize = 4;
}

fn use_nn<T: MyTrait>(_t: &T) -> usize {
    T::NN
}

fn main() {
    let a = MyStruct { abc: [1. ,2., 3., 4.] };
    let v = use_nn(&a);
    
    assert_eq!(v, 4); // passes
    assert_eq!(a.abc[0], 1.0); // passes
    
    let a = MyStruct { abc: [1. ,2., 3., 4., 5.] };
    /// error: ^^^^^^^^^^^^^^^^^^^^ expected an array with a fixed size of 4 elements, found one with 5 elements

}

1
Mad Physicist On

While not ideal, I've taken Chayim Friedman's comment as a possible solution. Instead of making the associated type be a constant indicating the number of elements, it can be the storage type itself:

trait Trait {
    type Buffer;
}

struct S<T: Trait> {
    field: T::Buffer,
}
0
Chayim Friedman On

An alternative solution to using an associated type is using typenum integers:

use nalgebra::base::dimension::{ToConst, U1};
use nalgebra::base::storage::{IsContiguous, RawStorageMut, Storage};
use nalgebra::base::{Matrix, Scalar};

trait ToVectorArrayStorage: ToConst {
    type ArrayStorage<T: Scalar>: RawStorageMut<T, <Self as ToConst>::Const>
        + Storage<T, <Self as ToConst>::Const>
        + IsContiguous;
}

macro_rules! impl_to_vector_array_storage {
    ( $( $num:literal => $typenum:ident, )* ) => {
        $(
            impl ToVectorArrayStorage for typenum::$typenum {
                type ArrayStorage<T: Scalar> = nalgebra::base::ArrayStorage<T, $num, 1>;
            }
        )*
    }
}
impl_to_vector_array_storage! {
    0=>U0, 1=>U1, 2=>U2, 3=>U3, 4=>U4, 5=>U5, 6=>U6, 7=>U7, 8=>U8, 9=>U9, 10=>U10,
    11=>U11, 12=>U12, 13=>U13, 14=>U14, 15=>U15, 16=>U16, 17=>U17, 18=>U18,
    19=>U19, 20=>U20,
}

trait Trait {
    type N: ToVectorArrayStorage;
}

type TypenumVector<T, N> =
    Matrix<T, <N as ToConst>::Const, U1, <N as ToVectorArrayStorage>::ArrayStorage<f64>>;

struct S<T: Trait> {
    field: TypenumVector<f64, T::N>,
}

Playground.