Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
newpavlov opened this issue May 5, 2019 · 30 comments
Open

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

newpavlov opened this issue May 5, 2019 · 30 comments
Labels
A-associated-items Area: Associated items (types, constants & functions) A-const-generics Area: const generics (parameters and arguments) C-bug Category: This is a bug. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@newpavlov
Copy link
Contributor

newpavlov commented May 5, 2019

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

@Centril Centril added A-const-generics Area: const generics (parameters and arguments) C-bug Category: This is a bug. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels May 5, 2019
@jonas-schievink jonas-schievink added the A-associated-items Area: Associated items (types, constants & functions) label May 5, 2019
@dtolnay
Copy link
Member

dtolnay commented May 10, 2019

Potentially related to #43408.

@Dylan-DPC-zz
Copy link

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 varkor added the const-generics-bad-diagnostics An error is correctly emitted, but is confusing, for `min_const_generics`. label Sep 13, 2020
@varkor
Copy link
Member

varkor commented Oct 1, 2020

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
Copy link
Member

varkor commented Oct 3, 2020

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.

JohnTitor added a commit to JohnTitor/rust that referenced this issue Oct 13, 2020
…stic, r=lcnr

`min_const_generics` diagnostics improvements

As disscussed in [zulip/project-const-generics/non-trivial anonymous constant](https://rust-lang.zulipchat.com/#narrow/stream/260443-project-const-generics/topic/non-trivial.20anonymous.20constants).

This is my first PR on the compiler.

@lcnr is mentoring me on this PR.

Related to rust-lang#60551.
JohnTitor added a commit to JohnTitor/rust that referenced this issue Oct 13, 2020
…stic, r=lcnr

`min_const_generics` diagnostics improvements

As disscussed in [zulip/project-const-generics/non-trivial anonymous constant](https://rust-lang.zulipchat.com/#narrow/stream/260443-project-const-generics/topic/non-trivial.20anonymous.20constants).

This is my first PR on the compiler.

@lcnr is mentoring me on this PR.

Related to rust-lang#60551.
JohnTitor added a commit to JohnTitor/rust that referenced this issue Oct 13, 2020
…stic, r=lcnr

`min_const_generics` diagnostics improvements

As disscussed in [zulip/project-const-generics/non-trivial anonymous constant](https://rust-lang.zulipchat.com/#narrow/stream/260443-project-const-generics/topic/non-trivial.20anonymous.20constants).

This is my first PR on the compiler.

@lcnr is mentoring me on this PR.

Related to rust-lang#60551.
@LeSeulArtichaut
Copy link
Contributor

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

@varkor varkor removed const-generics-bad-diagnostics An error is correctly emitted, but is confusing, for `min_const_generics`. const-generics-blocking labels Oct 19, 2020
@varkor
Copy link
Member

varkor commented Oct 19, 2020

Yes, thanks.

@parraman
Copy link

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
Copy link

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
Copy link
Member

varkor commented Jun 7, 2021

@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
Copy link

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
Copy link
Member

varkor commented Jun 7, 2021

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.

tarcieri added a commit to RustCrypto/traits that referenced this issue Jun 8, 2021
Ideally instead of specifying a `Curve::UInt` associated type, we could
instead specify a constant, and then pass that as a const generic
parameter to `crypto_bigint::UInt`.

Unfortunately that requires a lot more functionality than is implemented
in `min_const_generics`:

rust-lang/rust#60551

This commit makes a note of as much in the comments, calling it out as
future work.
@burdges
Copy link

burdges commented Feb 13, 2023

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
Copy link

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
Copy link

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
Copy link
Contributor

tgross35 commented Mar 25, 2023

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
Copy link

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
Copy link
Contributor

tgross35 commented Mar 25, 2023

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
Copy link

The stupid solution that I found

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

@hniksic
Copy link
Contributor

hniksic commented Sep 2, 2023

@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
Copy link
Member

kennytm commented Apr 29, 2024

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. Associated constants in traits can not be used in const generics #60551 (comment) — 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 Associated constants in traits can not be used in const generics #60551 (comment))

    #![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. Associated constants in traits can not be used in const generics #60551 (comment) — 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. Associated constants in traits can not be used in const generics #60551 (comment) — 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 
        [(); Self::D]:,
    {}
  5. Associated constants in traits can not be used in const generics #60551 (comment) — 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
    #![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,
        [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,
            }
        }
    }
    
    // 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<P: PL> {
        comp: P,
    }
    
    impl<P: PL> MathTrait for Physics<P> 
    {
        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; Physics::<P>::NDIM]: Sized,
    {
        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<P>` not `Physics<PhysicsComp>`.
                comp: self.data.clone(),
            };
            let mut solver = Solver::<Physics::<P>>::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
    #![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,
        [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,
            }
        }
    }
    
    // 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<P: PL> {
        comp: 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]) {
            let x = [1.0; Self::NDIM];
            data.copy_from_slice(&x);
        }
    }
    
    struct DoStuff<P: PL> {
        data: P,
    }
    
    impl<P: PL> DoStuff<P>
    where
        [f64; Physics::<P>::NDIM]: Sized,
    {
        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<P>` not `Physics<PhysicsComp>`.
                comp: self.data.clone(),
            };
            let mut solver = Solver::<Physics::<P>>::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?

rcarson3 added a commit to rcarson3/HelixSnail that referenced this issue Oct 14, 2024
Thanks to a github comment on an issue I was running into with consts it turns out we can simplify things more which is great: rust-lang/rust#60551 (comment)
I will look at simplifying things further now that my testing has also shown that not all the additional const bounds are needed for various functions if the compiler can deduce them.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-associated-items Area: Associated items (types, constants & functions) A-const-generics Area: const generics (parameters and arguments) C-bug Category: This is a bug. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests