rust-lang / rust

Empowering everyone to build reliable and efficient software.
https://www.rust-lang.org
Other
98.36k stars 12.72k forks source link

Associated constants in traits can not be used in const generics #60551

Open newpavlov opened 5 years ago

newpavlov commented 5 years ago

Initially reported here.

The following code:

trait Foo {
    const N: usize;
    fn foo() -> [u8; Self::N];
}

Playground

Results in a "no associated item named N found for type Self in the current scope" compilation error on current Nightly.

Note that associated constants in impl blocks work without any issues.

cc @varkor @yodaldevoid

dtolnay commented 5 years ago

Potentially related to https://github.com/rust-lang/rust/issues/43408.

Dylan-DPC-zz commented 4 years ago

The error now is:

error: constant expression depends on a generic parameter
 --> src/lib.rs:5:17
  |
5 |     fn foo() -> [u8; Self::N];
  |                 ^^^^^^^^^^^^^
  |
  = note: this may fail depending on what value the parameter takes
varkor commented 4 years ago

The error with min_const_generics enabled is:

error: generic parameters must not be used inside of non trivial constant values
 --> src/lib.rs:5:22
  |
5 |     fn foo() -> [u8; Self::N];
  |                      ^^^^^^^ non-trivial anonymous constants must not depend on the parameter `Self`
  |
  = note: type parameters are currently not permitted in anonymous constants

which can definitely be improved.

varkor commented 4 years ago

I think we ought to prioritise good diagnostics for min_const_generics from the beginning, so I'm making this issue as a blocker too.

LeSeulArtichaut commented 4 years ago

77825 landed, should const-generics-bad-diagnostics (and const-generics-blocking) be removed?

varkor commented 4 years ago

Yes, thanks.

parraman commented 3 years ago

I think the following problem is related to this issue. If not, I apologize in advance. I don't quite understand why this code works:

#![feature(min_const_generics)]

struct WithArray<T, const SIZE: usize> {
    data: [T; SIZE]
}

while this other one doesn't:

trait WithAConstant {
    const SIZE: usize;
}

struct WithArray<T, U: WithAConstant> {
    data: [T; U::SIZE]
}

This last one produces the following error:

error: generic parameters may not be used in const operations
  --> src/main.rs:13:27
   |
13 |     data: [T; U::SIZE]
   |                           ^^^^^^^ cannot perform const operation using `U`
   |
   = note: type parameters may not be used in const expressions

Thanks!

ChristopherRabotin commented 3 years ago

Hi there,

Is there a fix to this issue on the stable branch of Rust? This issue happens with rustc 1.52.1 (9bc8c42bb 2021-05-09).

Playground

Thanks

varkor commented 3 years ago

@parraman: sorry, I missed your comment. Your example should eventually work; it is a current implementation limitation. If you enable #![feature(const_evaluatable_checked)], it almost works (and probably can be made to work with a little tweaking to ensure U's variance is known):

#![feature(const_generics)]
#![feature(const_evaluatable_checked)]

trait WithAConstant {
    const SIZE: usize;
}

struct WithArray<T, U: WithAConstant> where [(); U::SIZE]: {
    data: [T; U::SIZE]
}

@ChristopherRabotin: this is a limitation of the current stable version of const generics that we hope to fix in a future version.

ChristopherRabotin commented 3 years ago

Okay thanks for the quick answer @varkor . If you were to bet, would you say this will be fix in the the next six months or more? I'm asking because I prefer keeping my crate working with rust stable, but I would be OK switching to nightly for a few months. Thanks

varkor commented 3 years ago

There are still a number of design and implementation questions that need to be resolved with const_evaluatable_checked before we can consider stabilising it, so likely not in the next six months.

hniksic commented 3 years ago

I encountered this limitation, and it makes const generics much harder to apply to my use case, where the behavior of a large chunk of code is fully customized by the trait. Customization normally includes associated types, and should also be able to include associated constants to be used in const generics. For example:

// n-dimensional point
struct Point<const NDims: usize>([f64; NDims]);

// configuration of various things, including number of dimensions
pub trait Config {
    const NDims: usize;
    // more config here...
}

// use Point in structs generic over C: Config
struct ColorSpace<C: Config> {
    data: Vec<(Point<C::NDims>, u8)>,
}

I understand that there are issues to resolve with the more general const evaluation (e.g. panic handling), but couldn't it be possible to refer to trait associated constants as a special case? I.e. have the above compile, but not the more complex Point<{C::NDims * 2}> or Point<{arbitrary_const_fn(C::NDims)}>?

Currently the only fix is to make the trait generic over NDims, but it's both conceptually wrong (the trait needs to choose NDims that makes sense for the configuration) and makes the API harder to use because an additional const generic creeps in everywhere. If Config needed to define multiple such constants, the problem would be that much worse.

josephg commented 3 years ago

Just chiming in to say I'm running into the same problem.

I have a bunch of configuration parameters to tune a custom b-tree. Passing in a trait object for configuration makes my code much cleaner here; if for no other reason so I don't need to copy-paste <E: EntryTraits, I: TreeIndex<E>, const INTERNAL_CHILDREN: usize, const INTERNAL_CHILDREN: usize>, ...> in about 20 places throughout my code.

I'm running into the same problem as @hniksic. I'm running into the awkward problem that I can't use the const field members in my trait to size some internal arrays. Now that const generics have landed, I'd love some love on this!

newpavlov commented 3 years ago

It looks like the following code successfully compiles on nigthly:

#![feature(generic_const_exprs)]

trait Foo {
    const N: usize;
    fn foo() -> [u8; Self::N];
}

Should I close this issue in favor of #76560?

hniksic commented 3 years ago

@newpavlov Please note that the example from the previous comment (that defines ColorSpace type) deosn't compile on current nightly. The compiler requires changing Point<C::NDims> to Point<{C::NDims}> because it treats C::NDims as a full-featured expression rather than a constant. But making that change still fails to compile, citing an "unconstrained generic constant" and suggesting a bound of where [(); {C::NDims}]:. Adding that bound leads to further errors about "unused parameter C" which I'm not sure how to address. [EDIT: as of Rust 1.58.0-nightly the version with the where [(); C::NDims]: bound does compile, though the bound looks very strange and I don't really understand it.]

It would be nice to have this resolved even without the full support for expressions in generic parameters. But even if that's not possible, the feature doesn't appear to work just yet even with generic_const_exprs, so the issue shouldn't be closed at this point.

tarcieri commented 3 years ago

Somewhat related to this issue that I just discovered today: as an alternative to associated constants, it's possible to parameterize a trait with a const generic today on stable Rust using only min_const_generics:

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=1b2ad1c2f09b2c468f78b393a42e1dad

trait Foo<const N: usize> {
    fn do_x(&self) -> [u8; N];
}

struct Bar;

impl Foo<42> for Bar {
    fn do_x(&self) -> [u8; 42] {
        [0u8; 42]
    }
}

It's not quite the same thing, but it might be good enough for many of these potential applications.

voidc commented 3 years ago

Adding that bound leads to further errors about "unused parameter C" which I'm not sure how to address.

The unused parameter error should disappear now that #89829 has been merged. (see also issue #80977)

mooman219 commented 2 years ago

I'm trying to write this on nightly with #![feature(generic_const_exprs)]

pub trait Foo {
    const COUNT: usize;
    const NAMES: [&'static str; Self::COUNT];
}

pub struct Bar<T: Foo> {
    name_lookup: [resource::UniformLocation; T::COUNT]
}

But I cannot

error: unconstrained generic constant
  --> src\test.rs:18:5
   |
18 |     const NAMES: [&'static str; Self::COUNT];
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = help: try adding a `where` bound using this expression: `where [(); Self::COUNT]:`

error: unconstrained generic constant
  --> src\test.rs:22:18
   |
22 |     name_lookup: [resource::UniformLocation; T::COUNT],
   |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = help: try adding a `where` bound using this expression: `where [(); T::COUNT]:`

I'm trying to avoid writing pub trait Foo<const COUNT: usize> because it results in a lot of boilerplate I need to propagate : (.

linclelinkpart5 commented 2 years ago

I'm trying to write this on nightly with #![feature(generic_const_exprs)]

pub trait Foo {
    const COUNT: usize;
    const NAMES: [&'static str; Self::COUNT];
}

pub struct Bar<T: Foo> {
    name_lookup: [resource::UniformLocation; T::COUNT]
}

But I cannot

error: unconstrained generic constant
  --> src\test.rs:18:5
   |
18 |     const NAMES: [&'static str; Self::COUNT];
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = help: try adding a `where` bound using this expression: `where [(); Self::COUNT]:`

error: unconstrained generic constant
  --> src\test.rs:22:18
   |
22 |     name_lookup: [resource::UniformLocation; T::COUNT],
   |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = help: try adding a `where` bound using this expression: `where [(); T::COUNT]:`

I'm trying to avoid writing pub trait Foo<const COUNT: usize> because it results in a lot of boilerplate I need to propagate : (.

I'm in this exact same scenario, I'm trying to avoid propagating the N generic parameter everywhere in my codebase. Is this currently possible with generic_const_exprs?

porky11 commented 2 years ago

I have a similar problem:

trait VectorSpace
where
    Self: ...,
    Self: Into<[Self::Scalar, Self::D]> + From<[Self::Scalar, Self::D]>,
{
    type Scalar;
    const D: usize;
}

Wouldn't this be the next thing, that should be stabilized? Doesn't seem too complicated.

rcarson3 commented 2 years ago

I believe this is related to this issue as well on nightly, but I'm running into the following issue with a more complicated piece of code I'm running on. However, I've worked out a simpler example here: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=f15f265d88a25cfa9fc8822cf3c5950e

The compiler can't figure out that the bound is already met even though all the values for it are known at compile time. I've run into similar issues with this in the past where I have arrays with defined const size but the compiler can't have them swap places, and it always wants me to add additional where clauses for those things...

#![no_std]
#![allow(incomplete_features)]
#![feature(generic_const_exprs)]

trait MathTrait {

    const NDIM: usize;
    fn calculate_math(&mut self, data: &mut [f64]);

}

struct Solver<'a, M>
where
    M: MathTrait + Sized,
    [f64; M::NDIM]: Sized,
{
    pub x: [f64; M::NDIM],
    problem: &'a mut M,
}

impl<'a, M> Solver<'a, M> 
where
    M: MathTrait + Sized,
    [f64; M::NDIM]: Sized,
{
    pub fn new(problem: &'a mut M ) -> Solver<'a, M> {
        Solver::<'a, M> {
            x: [0.0_f64; M::NDIM],
            problem,
        }
    }
}

trait PL {
    const NSIZE: usize;
}

struct PhysicsComp{}

impl PL for PhysicsComp {
    const NSIZE: usize = 32;
}

struct Physics<P: PL> {
    comp: P,
}

impl<P: PL> MathTrait for Physics<P> 
where
    [f64; P::NSIZE]: Sized,
{
    const NDIM: usize = P::NSIZE;

    fn calculate_math(&mut self, data: &mut [f64]) {
        /* do math here or whatever */
    }
}

struct DoStuff<P: PL> {
    data: P,
}

impl<P: PL> DoStuff<P>
where
    [f64; P::NSIZE]: Sized, // Bound that is equivalent to the error of [f64; M::NDIM]: Sized,
{
    fn problem(&self) -> bool {
        let mut phys = Physics {
            comp: PhysicsComp {},  
        };
        // This line fails quite hard
        let mut solver = Solver::<Physics::<P>>::new(&mut phys);
        true
    }
}

fn main() {
    let failure = DoStuff {
        data: PhysicsComp {},
    };
    failure.problem();
}
error[[E0599]](https://doc.rust-lang.org/nightly/error-index.html#E0599): no function or associated item named `new` found for struct `Solver<'_, Physics<P>>` in the current scope
  [--> src/main.rs:76:50
](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=f15f265d88a25cfa9fc8822cf3c5950e#)   |
12 | / struct Solver<'a, M>
13 | | where
14 | |     M: MathTrait + Sized,
15 | |     [f64; M::NDIM]: Sized,
...  |
18 | |     problem: &'a mut M,
19 | | }
   | |_- function or associated item `new` not found for this
...
76 |           let mut solver = Solver::<Physics::<P>>::new(&mut phys);
   |                                                    ^^^ function or associated item cannot be called on `Solver<'_, Physics<P>>` due to unsatisfied trait bounds

error: unconstrained generic constant
  [--> src/main.rs:76:26
](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=f15f265d88a25cfa9fc8822cf3c5950e#)   |
76 |         let mut solver = Solver::<Physics::<P>>::new(&mut phys);
   |                          ^^^^^^^^^^^^^^^^^^^^^^
   |
   = help: try adding a `where` bound using this expression: `where [(); M::NDIM]:`
note: required by a bound in `Solver`
  [--> src/main.rs:15:11
](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=f15f265d88a25cfa9fc8822cf3c5950e#)   |
12 | struct Solver<'a, M>
   |        ------ required by a bound in this
...
15 |     [f64; M::NDIM]: Sized,
   |           ^^^^^^^ required by this bound in `Solver`

For more information about this error, try `rustc --explain E0599`.
burdges commented 1 year ago

If I understand, rustc cannot correctly identify some bound when using the associated constant internally to a default method? https://play.rust-lang.org/?version=nightly&mode=debug&edition=2015&gist=e1114345b12197fdb922ef1acb2e80ae

chrismooredev commented 1 year ago

Similarly to burdges' playground, I'd also like to throw in the ugly syntax necessary for using bool associated constants. It requiring that bound, and especially having to cast it to a usize, is not intuitive.

I planned to use this feature similarly to my playground, have a trait with a ton of default implementations for a few types, while still being able to use const type parameters as feature/optimization flags for different internal algorithms, operating on those types.

Playground Link

#![feature(generic_const_exprs)]

fn _internal_impl<const FLAG: bool>() -> &'static str {
    if FLAG {
        "run for accuracy"
    } else {
        "run for speed"
    }
}

trait Operation {
    const FLAG: bool;
    fn run() -> &'static str where [(); Self::FLAG as usize]: {
        // default implementation
        _internal_impl::<{ Self::FLAG }>()
    }
}
alexpyattaev commented 1 year ago

Can someone explain to a mere mortal what is the purpose of

where [(); TRAIT::CONST_NAME] :

syntax? What does it achieve in particular? I've seen something about variance but it gets muddy from there.

tgross35 commented 1 year ago

Can someone explain to a mere mortal what is the purpose of

where [(); AssociatedType::CONST_NAME] :

My very naive understanding is that without the bound either (1) [T; TypeA::LEN] and [T; TypeB::LEN] would be considered the same in the type system even if TypeA::LEN != TypeB::LEN (because associated consts behave differently from associated types) or (2) a [T; TypeA::LEN] in two different places would not evaluate to the same type because it has no covariance to itself. I'm not sure which of these two are more accurate, but I believe that it's a result of [T; N] being covariant to T but having no relationship to N - and I think that is what this syntax is constraining.

I don't think the syntax is intended to stay - at least I sure hope not, it's pretty unfriendly and doesn't at all hint what it's doing. I think it's just some existing syntax that happens to do the trick. If it needs to be expressed, then some std::ops type traits would imho be a better choice:

struct Foo<T, U: WithAConstant>
    // Only one of these options
    where AnyArray<U::Size>: Covariant
    where AnyArray<U::Size>: CovariantOn<U::SIZE>
{
    data: [T; U::SIZE]
}

But I think the compiler should be able to infer the correct usage and just error if something is too complex for a simple representation. At least I hope that's possible, because any sort of bound is unneeded noise for something that "should work" in the default case.

If inference isn't possible, I'd be kind of curious to see some examples explaining why. Or at least a better explanation from somebody about what the actual variance issue is

alexpyattaev commented 1 year ago

Well this makes the purpose clear. As you suggested, this will likely go away eventually, since for more "normal" cases the conversion from [T; N] to [T;3] when N=3 happens automatically without messy syntax.

The bigger question that is still bugging me is how someone came up with that construct and what does it truly mean...

tgross35 commented 1 year ago

It doesn't seem like anybody came up with the syntax specifically for this feature, I just think it's something that exists in the type system already. For example, on stable this compiles (even though it doesn't do anything):

struct Foo where [(); 100]: {}

And it seems like you can use any type, () just sort of hints "can be any type" even though that's not exactly what it says. This compiles on nightly:

#![feature(generic_const_exprs)]

trait WithAConstant {
    const SIZE: usize;
}

struct WithArray<T, U: WithAConstant>
where [String; U::SIZE]: // using any type here has the same result
{
    data: [T; U::SIZE]
}

Since the syntax already exists, I don't think anything syntax-related would at all block stabilization of generic_const_exprs. I think it makes sense instead to create a separate feature like const_variance_inference that has the purpose of eliminating its need, which could stabilize on its own time.

But I don't know if this has been discussed anywhere already, and would be curious to hear from anybody more involved in the RFC / decision making of this feature.

As far as status of stabilizing generic_const_exprs It seems like there are quite a few issues open that be blocking https://github.com/rust-lang/project-const-generics/issues?q=is%3Aissue+is%3Aopen+label%3AA-generic-exprs

(Not sure if rustbot will let me do this since it's not my issue, but seems like this one belongs in that group too since it's related)

@rustbot label +A-generic-exprs

(edit: nope 😔)

igorechek06 commented 1 year ago

The stupid solution that I found

trait Trait<const SIZE: usize> {
    const SIZE: usize = SIZE;
    fn fun() -> [u8; SIZE];
}
hniksic commented 1 year ago

@igorechek06 It's great that you found a workaround that works for you, but I need to point out that your solution doesn't help when you need the trait implementation to specify the value of the constant (SIZE in your example). That's the situation in the description of this issue, as well as the one presented in my earlier comment.

kennytm commented 6 months ago

as of today all examples raised in this issue can be made compiled with sufficient #![feature]s and adding where [(); X::CONST]: and splitting trait to avoid E0391.

  1. OP — works as-is.

    #![allow(incomplete_features, dead_code)]
    #![feature(generic_const_exprs)]
    trait Foo {
        const N: usize;
        fn foo() -> [u8; Self::N];
    }
  2. https://github.com/rust-lang/rust/issues/60551#issuecomment-882031857 — requires (1) using Point<{C::NDims}> to tell the compiler it is a constant not a type (2) adding where [(); C::NDims]: to avoid the "unconstrained generic constant" error (as noted in https://github.com/rust-lang/rust/issues/60551#issuecomment-917601657)

    #![allow(incomplete_features, dead_code, non_upper_case_globals)]
    #![feature(generic_const_exprs)]
    struct Point<const NDims: usize>([f64; NDims]);
    pub trait Config {
        const NDims: usize;
    }
    struct ColorSpace<C: Config> where [(); C::NDims]: {
        data: Vec<(Point<{C::NDims}>, u8)>,
    }
  3. https://github.com/rust-lang/rust/issues/60551#issuecomment-991440346 — requires #![feature(generic_const_items)] to put that where clause on const NAMES.

    #![allow(incomplete_features, dead_code)]
    #![feature(generic_const_exprs, generic_const_items)]
    pub trait Foo {
        const COUNT: usize;
        const NAMES: [&'static str; Self::COUNT] where [(); Self::COUNT]:;
    }
    
    pub struct Bar<T: Foo> where [(); T::COUNT]: {
        name_lookup: [String; T::COUNT]
    }
  4. https://github.com/rust-lang/rust/issues/60551#issuecomment-1066699575 — original definition has a cycle.

    error[E0391]: cycle detected when building an abstract representation for `VectorSpace::{constant#0}`
     --> src/lib.rs:5:31
      |
    5 |     Self: Into<[Self::Scalar; Self::D]> + From<[Self::Scalar; Self::D]>,
      |                               ^^^^^^^
      |
    note: ...which requires building THIR for `VectorSpace::{constant#0}`...
     --> src/lib.rs:5:31
      |
    5 |     Self: Into<[Self::Scalar; Self::D]> + From<[Self::Scalar; Self::D]>,
      |                               ^^^^^^^
    note: ...which requires type-checking `VectorSpace::{constant#0}`...
     --> src/lib.rs:5:31
      |
    5 |     Self: Into<[Self::Scalar; Self::D]> + From<[Self::Scalar; Self::D]>,
      |                               ^^^^^^^
      = note: ...which again requires building an abstract representation for `VectorSpace::{constant#0}`, completing the cycle

    This can be workedaround/fixed by splitting VectorSpace into two traits. Needs someone more familiar to confirm if it is expected or not.

    #![allow(incomplete_features, dead_code)]
    #![feature(generic_const_exprs)]
    trait VectorSpaceBase {
        type Scalar;
        const D: usize;
    }
    
    trait VectorSpace: VectorSpaceBase 
        + Into<[Self::Scalar; Self::D]> 
        + From<[Self::Scalar; Self::D]>
    where 
    {}
  5. https://github.com/rust-lang/rust/issues/60551#issuecomment-1073054442 — very complicated example. First this can be made compilable with some changes

    @@ -25,11 +25,13 @@
         }
     }
    
    -trait PL {
    +// NOTE: Added `Clone` superbound, see the other NOTE below for reason.
    +trait PL: Clone {
         const NSIZE: usize;
     }
    
    +#[derive(Clone)]
     struct PhysicsComp {}
    
     impl PL for PhysicsComp {
         const NSIZE: usize = 32;
    @@ -40,8 +42,6 @@
     }
    
     impl<P: PL> MathTrait for Physics<P> 
    -where
    -    [f64; P::NSIZE]: Sized,
     {
         const NDIM: usize = P::NSIZE;
    
    @@ -56,13 +56,14 @@
    
     impl<P: PL> DoStuff<P>
     where
    -    [f64; P::NSIZE]: Sized, // Bound that is equivalent to the error of [f64; M::NDIM]: Sized,
    +    [f64; Physics::<P>::NDIM]: Sized,
     {
         fn problem(&self) -> bool {
             let mut phys = Physics {
    -            comp: PhysicsComp {},  
    +            // NOTE: I've changed `PhysicsComp {}` here to `self.data.clone()`
    +            // because the `solver` below expects a `Physics<P>` not `Physics<PhysicsComp>`.
    +            comp: self.data.clone(),
             };
    -        // This line fails quite hard
             let mut solver = Solver::<Physics::<P>>::new(&mut phys);
             true
         }
    Full code ```rust #![allow(incomplete_features, dead_code, unused_mut, unused_variables)] #![feature(generic_const_exprs)] trait MathTrait { const NDIM: usize; fn calculate_math(&mut self, data: &mut [f64]); } struct Solver<'a, M> where M: MathTrait + Sized, { pub x: [f64; M::NDIM], problem: &'a mut M, } impl<'a, M> Solver<'a, M> where M: MathTrait + Sized, { pub fn new(problem: &'a mut M) -> Solver<'a, M> { Solver::<'a, M> { x: [0.0_f64; M::NDIM], problem, } } } // NOTE: Added `Clone` superbound, see the other NOTE below for reason. trait PL: Clone { const NSIZE: usize; } #[derive(Clone)] struct PhysicsComp {} impl PL for PhysicsComp { const NSIZE: usize = 32; } struct Physics { comp: P, } impl MathTrait for Physics

    { const NDIM: usize = P::NSIZE; fn calculate_math(&mut self, data: &mut [f64]) { /* do math here or whatever */ } } struct DoStuff { data: P, } impl DoStuff

    where { fn problem(&self) -> bool { let mut phys = Physics { // NOTE: I've changed `PhysicsComp {}` here to `self.data.clone()` // because the `solver` below expects a `Physics

    ` not `Physics`. comp: self.data.clone(), }; let mut solver = Solver::>::new(&mut phys); true } } fn main() { let failure = DoStuff { data: PhysicsComp {}, }; failure.problem(); } ```

    but this fails hard you actually try to implement Physics::<P>::calculate_math() to do anything interesting that used Self::NDIM in a type — you need to add back the [f64; Self::NDIM]: Sized bound and that triggers E0391 (type dependency cycle) again. Again like Case #‌4 I can workaround it by splitting MathTrait into two traits.

    @@ -1,5 +1,8 @@
    -trait MathTrait {
    +trait MathTraitBase {
         const NDIM: usize;
    +}
    +
    +trait MathTrait: MathTraitBase {
         fn calculate_math(&mut self, data: &mut [f64]);
     }
    
    @@ -41,12 +44,18 @@
         comp: P,
     }
    
    -impl<P: PL> MathTrait for Physics<P>
    +impl<P: PL> MathTraitBase for Physics<P>
     {
         const NDIM: usize = P::NSIZE;
    +}
    +
    +impl<P: PL> MathTrait for Physics<P>
    +where
    +    [f64; Self::NDIM]: Sized,
    +{
         fn calculate_math(&mut self, data: &mut [f64]) {
    -        /* do math here or whatever */
    +        let x = [1.0; Self::NDIM];
    +        data.copy_from_slice(&x);
         }
     }
    
    @@ -65,6 +74,7 @@
                 comp: self.data.clone(),
             };
             let mut solver = Solver::<Physics::<P>>::new(&mut phys);
    +        solver.problem.calculate_math(&mut solver.x);
             true
         }
     }
    Full code ```rust #![allow(incomplete_features, dead_code)] #![feature(generic_const_exprs)] trait MathTraitBase { const NDIM: usize; } trait MathTrait: MathTraitBase { fn calculate_math(&mut self, data: &mut [f64]); } struct Solver<'a, M> where M: MathTrait + Sized, { pub x: [f64; M::NDIM], problem: &'a mut M, } impl<'a, M> Solver<'a, M> where M: MathTrait + Sized, { pub fn new(problem: &'a mut M) -> Solver<'a, M> { Solver::<'a, M> { x: [0.0_f64; M::NDIM], problem, } } } // NOTE: Added `Clone` superbound, see the other NOTE below for reason. trait PL: Clone { const NSIZE: usize; } #[derive(Clone)] struct PhysicsComp {} impl PL for PhysicsComp { const NSIZE: usize = 32; } struct Physics { comp: P, } impl MathTraitBase for Physics

    { const NDIM: usize = P::NSIZE; } impl MathTrait for Physics

    where { fn calculate_math(&mut self, data: &mut [f64]) { let x = [1.0; Self::NDIM]; data.copy_from_slice(&x); } } struct DoStuff { data: P, } impl DoStuff

    where { fn problem(&self) -> bool { let mut phys = Physics { // NOTE: I've changed `PhysicsComp {}` here to `self.data.clone()` // because the `solver` below expects a `Physics

    ` not `Physics`. comp: self.data.clone(), }; let mut solver = Solver::>::new(&mut phys); solver.problem.calculate_math(&mut solver.x); true } } fn main() { let failure = DoStuff { data: PhysicsComp {}, }; failure.problem(); } ```

So I think the questions remain for this issue are:

  1. can this work without introducing those annoying where [(); Self::CONST]: bounds?
  2. are those E0391 (type dependency cycle) errors still expected if we did get rid of those where bounds? and must we break the cycle by splitting the trait?