Skip to content

Tracking Issue for const_fn_trait_bound #93706

Closed

Description

@PatchMixolydic

This is a tracking issue for trait bounds on const fns. The feature gate for the issue is #![feature(const_fn_trait_bound)]. This is not to be confused with the "trait impl must be const in const contexts" syntax, ~const Trait, which is a part of const_trait_impl and is tracked in #67792.

About tracking issues

Tracking issues are used to record the overall progress of implementation.
They are also used as hubs connecting to other relevant issues, e.g., bugs or open design questions.
A tracking issue is however not meant for large scale discussion, questions, or bug reports about a feature.
Instead, open a dedicated issue for the specific matter and add the relevant feature gate label.

Steps

Unresolved Questions

Implementation history

@rustbot modify labels +A-const-fn +A-traits

Activity

  1. added
    C-tracking-issueCategory: An issue tracking the progress of sth. like the implementation of an RFC
    on Feb 6, 2022
  2. added 3 commits that reference this issue on Feb 6, 2022
  3. added 2 commits that reference this issue on Feb 7, 2022
  4. fee1-dead commented on Feb 7, 2022

    @fee1-dead
    Member

    cc @rust-lang/wg-const-eval: Should we remove this feature because of #90912?

  5. oli-obk commented on Feb 7, 2022

    @oli-obk
    Contributor

    Yes, together with function pointers and trait objects. We just need a stabilization PR and a summary for the lang team

  6. eholk commented on Feb 11, 2022

    @eholk
    Contributor

    I have a stabilization PR at #93827 and a documentation PR now at rust-lang/reference#1166.

  7. added a commit that references this issue on Mar 7, 2022
  8. jplatte commented on Mar 22, 2022

    @jplatte
    Contributor

    This has been stabilized in #93827, I'd really prefer if we could take a step back, revert that stabilization and consider an edition-based flow for getting ?const instead of ~const afterall:

    The few currently allowed bounds in const fn could be transitioned to ?const via deprecation (plus more ?const bounds allowed) in this edition, then the next edition can change the meaning of trait bounds without ?const in const fn and we can have the thing we originally wanted.

    I've voiced this idea on the tracking issue for the impl const Trait RFC earlier: #67792 (comment)

  9. jhpratt commented on Mar 22, 2022

    @jhpratt
    Member

    What's wrong with having the stabilization that's already landed and switching to ?const in a future edition?

  10. RalfJung commented on Mar 22, 2022

    @RalfJung
    Member

    It's not just the bounds, it's also function pointers and trait objects that already made me advocate for this change long before the bounds stabilization hole was discovered.

    A T: Trait bound should be symmetric with &dyn Trait, so if the former implicitly gains constness so should the latter. However, this is stable since forever:

    trait NotConstTrait {
        fn action(&self);
    }
    
    impl NotConstTrait for i32 {
        fn action(&self) {
            println!("{}", self);
        }
    }
    
    const X: &dyn NotConstTrait = &13;
  11. jplatte commented on Mar 22, 2022

    @jplatte
    Contributor

    What's wrong with having the stabilization that's already landed and switching to ?const in a future edition?

    Then you'd be stabilizing something just to deprecate it short after. But I guess it would be fine.

    It's not just the bounds, it's also function pointers and trait objects that already made me advocate for this change long before the bounds stabilization hole was discovered.

    I think I've seen this argument before but forgot about it. To me personally, with how different and magic dyn already is, I'm not convinced by this argument but I can see where you're coming from.

  12. RalfJung commented on Mar 22, 2022

    @RalfJung
    Member

    To add to the argument, consider the syntactic parallel between impl and dyn:

    const fn foo(x: &dyn Trait);
    const fn foo(x: impl Trait);
    const fn foo<T: Trait>(x: T);

    The last two are the same by definition; the first two IMO should be the same as everything else would be really confusing.

    If you consider dyn too much magic, let me note that this also affects const fn foo(x: fn()), but the syntactic symmetry is more striking with dyn. These (traits bounds, dyn, fn) are all just different ways to "abstract over code", and IMO their interaction with const should be consistent across the board.

  13. fee1-dead commented on Mar 22, 2022

    @fee1-dead
    Member

    consider this:

    const fn foo(x: &dyn Trait);
    const fn foo(x: impl Trait);
    const fn foo<T: Trait>(x: T);
    const fn foo(x: fn());

    If we go with the ~const approach, all of these will not impose additional requirements for the callers.

    If we do ?const, some of them will be inconsistent, even if we made it consistent for the next edition it is going to be a pain to migrate, because all types (including fn pointers) now needs ?const to have the same behavior as before.

    When you are arguing for ?const you are arguing for the fact that all of const trait bounds, const dyn traits, const impl traits, const fn pointers are used more frequently than their non-const versions in const contexts.

  14. RalfJung commented on Mar 22, 2022

    @RalfJung
    Member

    If we do ?const, some of them will be inconsistent,

    Well, we could make all of them require ?const, but that is super backwards incompatible.
    (In const fn, fn and dyn are unstable -- or just have recently been stabilized together with the trait bounds. However, in const/static they are stable and having the same type mean something different in const vs const fn sounds like a Bad Idea.)

  15. jplatte commented on Mar 29, 2022

    @jplatte
    Contributor

    To add to the argument, consider the syntactic parallel between impl and dyn:

    const fn foo(x: &dyn Trait);
    const fn foo(x: impl Trait);
    const fn foo<T: Trait>(x: T);

    The last two are the same by definition; the first two IMO should be the same as everything else would be really confusing.

    I thought about this for a while, and I am not convinced for two reasons:

    First, I don't see why you would ever use dyn Trait in a const fn but second and more importantly, I can now articulate this "dyn Trait is just inherently different" feeling I had before. Take the case of auto traits:

    // Return type is !Send !Sync even if the type underpinning `x` is
    const fn foo(x: &dyn Trait) -> &dyn Trait { x }
    // Return type Send and/or Sync if the type of `x` is
    const fn foo(x: impl Trait) -> impl Trait { x }

    It's not something people think about a lot, but once you do it's actually intuitive (IMHO): Because of the type erasure, you can't propagate auto traits.

    Additionally, and this has been stated before but I think it's important to bring it up again: Requiring a const impl in a const context is simply the way more useful default, and it's a backwards-compatible change to add ?const. People don't need to learn about all of this concept of "needs to be const impl if called in const context" until they actually need itm or come across an API that uses ?const.

    Finally, and this isn't really a strong argument for either side I think, but I feel like I should point out that there are conceptually three cases for trait bounds in generic const fn. This could probably be seen two ways, let me try to explain with a table:

    "simpler syntax: less constraints" "simpler syntax: less weird"
    requires const impl T: const Trait (?) T: const Trait (?)
    requires const impl in const context T: ~const Trait T: Trait
    requires a regular impl T: Trait T: ?const Trait
  16. RalfJung commented on Mar 30, 2022

    @RalfJung
    Member

    I don't see why you would ever use dyn Trait in a const fn

    For the same reasons you would use dyn Trait in a regular fn.

    Return type Send and/or Sync if the type of x is

    Yeah, return-type impl Trait is semi-transparent, and in particular is transparent for auto traits. That's an odd quirk, but nothing fundamental and not conceptually very relevant in my eyes.

    Requiring a const impl in a const context is simply the way more useful default,

    I agree! IMO that also applies to dyn Trait and fn types.

    But I don't agree strong enough to be willing to sacrifice consistency for this.

    there are conceptually three cases for trait bounds in generic const fn

    Yes, I agree.
    The "less weird" column however is inconsistent with how ? syntax is used elsewhere -- with ?Sized, we have that writing nothing is equivalent to having a Sized bound. So I would expect that with ?const, this indicates opting out of a default const, so explicitly writing const should be the same as writing nothing.

    What this goes to show is that the situation is fundamentally different from ?Sized.

  17. jplatte commented on Mar 30, 2022

    @jplatte
    Contributor

    Requiring a const impl in a const context is simply the way more useful default,

    I agree! IMO that also applies to dyn Trait and fn types.

    Oh, I think I see. Theoretically we could have this behavior even for non-generic const fn, there's no technical reasons preventing it. I didn't really consider that.

    with ?Sized, we have that writing nothing is equivalent to having a Sized bound. So I would expect that with ?const, this indicates opting out of a default const

    Heh, so I guess this gives rise to yet another possible (but to me very unlikely) option:

    semantics syntax
    requires const impl T: const Trait (?)
    requires const impl in const context T: Trait or T: ~const Trait
    requires a regular impl T: ?~const Trait
  18. oli-obk commented on May 13, 2022

    @oli-obk
    Contributor

    This has been stabilized in #93827

  19. added
    A-const-evalArea: Constant evaluation, covers all const contexts (static, const fn, ...)
    on Dec 1, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-const-evalArea: Constant evaluation, covers all const contexts (static, const fn, ...)A-trait-systemArea: Trait systemC-tracking-issueCategory: An issue tracking the progress of sth. like the implementation of an RFC

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions