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

Foo<T>: Trait bounds (T is a type param) are allowed in const fns #83452

Closed
rodrimati1992 opened this issue Mar 24, 2021 · 31 comments
Closed

Foo<T>: Trait bounds (T is a type param) are allowed in const fns #83452

rodrimati1992 opened this issue Mar 24, 2021 · 31 comments
Labels
A-const-eval Area: Constant evaluation, covers all const contexts (static, const fn, ...) A-stability Area: `#[stable]`, `#[unstable]` etc. C-bug Category: This is a bug. F-const_trait_impl `#![feature(const_trait_impl)]`

Comments

@rodrimati1992
Copy link
Contributor

rodrimati1992 commented Mar 24, 2021

I tried this code:

pub trait Const {
    const C: u8;
}

impl Const for u8 {
    const C: u8 = 3;
}
pub struct Wrapper<T>(T);
impl<T: Const> Const for Wrapper<T> {
    const C: u8 = T::C;
}

const fn foo<T>() -> u8 
where
    Wrapper<T>: Const,
{
    <Wrapper<T>>::C
}

fn main(){
    const D: u8 = foo::<u8>();
    dbg!(D);
}

I expected to see this code not compile, because it bounds types by non-auto traits in const fns.

Instead, this happened: The code compiles without errors, and prints 3
This does error with T: Const bounds as expected.

Meta

rustc --version --verbose:

rustc 1.53.0-nightly (673d0db5e 2021-03-23)
binary: rustc
commit-hash: 673d0db5e393e9c64897005b470bfeb6d5aec61b
commit-date: 2021-03-23
host: i686-unknown-linux-gnu
release: 1.53.0-nightly
LLVM version: 12.0.0
@rodrimati1992 rodrimati1992 added the C-bug Category: This is a bug. label Mar 24, 2021
@jonas-schievink jonas-schievink added A-const-eval Area: Constant evaluation, covers all const contexts (static, const fn, ...) A-stability Area: `#[stable]`, `#[unstable]` etc. labels Mar 24, 2021
@slightlyoutofphase
Copy link
Contributor

slightlyoutofphase commented Mar 24, 2021

It's not clear to me why this would be expected not to work. The bounds are upheld correctly by the compiler in precisely the same way as they are for non-const fns. For example if you do const D: usize = foo::<usize>();, you properly get error[E0277]: the trait bound usize: Const is not satisfied.

Same thing if you call foo in a runtime context, like let d: usize = foo::<usize>();, since every const fn is also by default a regular fn if called in a non-const context. I cannot personally think of any "upsides" of the particular trait bound restriction on const fns you're talking about actually existing.

@rodrimati1992
Copy link
Contributor Author

rodrimati1992 commented Mar 24, 2021

@slightlyoutofphase That's an argument for allowing all bounds in const fns

If you look at what's proposed in https://github.com/oli-obk/rfcs/blob/const_generic_const_fn_bounds/text/0000-const-generic-const-fn-bounds.md#guide-level-explanation . The foo function (from my example) will require Const to be implemented as impl const Const for u8 and impl<T: Const> const Const for Wrapper<T> when foo is used in a const context, that will break this example.

@slightlyoutofphase
Copy link
Contributor

slightlyoutofphase commented Mar 24, 2021

@slightlyoutofphase That's an argument for allowing all bounds in const fns

I guess my overall point is that I'm not personally aware of anywhere it's been specifically stated that this shouldn't work, and why it shouldn't work.

If you look at what's proposed in https://github.com/oli-obk/rfcs/blob/const_generic_const_fn_bounds/text/0000-const-generic-const-fn-bounds.md#guide-level-explanation . The foo function (from my example) will require Const to be implemented as impl const Const for u8 and impl<T: Const> const Const for Wrapper<T> when foo is used in a const context, that will break this example.

Your Const trait has no methods though. There is no reason for it to need to be implemented specifically as const.

@rodrimati1992
Copy link
Contributor Author

rodrimati1992 commented Mar 24, 2021

Your Const trait has no methods though

It doesn't matter, you're allowed to add defaulted methods and that shouldn't break downstream users, which is why impl const Const needs to be required.

This is not the issue to figure out how to solve problems with const trait impls and bounds though, it's about making sure that there's no forward compatibility bugs.

This isn't allowed now:

const fn foo<T: Const>() -> u8 {
    T::C
}

and I see no reason why it should be any different with Foo<T>: Const

@jonas-schievink jonas-schievink added the F-const_trait_impl `#![feature(const_trait_impl)]` label Mar 24, 2021
@slightlyoutofphase
Copy link
Contributor

slightlyoutofphase commented Mar 24, 2021

It doesn't matter, you're allowed to add defaulted methods and that shouldn't break downstream users, which is why impl const Const needs to be required.

It doesn't need to be "required". The only limitation currently is that a user cannot directly write:

fn foo<T: const Const>()

meaning T has to specifically be something that implements Const as const. However, as far as I'm aware, some way of writing that (even if not with the exact syntax I just used) is planned, but just not implemented yet.

This isn't allowed now:

Yeah it is, if you use the #![feature(const_fn)] flag. Which anyone trying to do anything complex with const fn functionality specifically on the nightly compiler will be.

@jonas-schievink
Copy link
Contributor

The problem is that the code posted above is accepted on stable, but the intention of the const_trait_impl feature is that all trait bounds on const fns will require an impl const Trait, unless you use T: ?const Trait syntax to opt out, which would cause the code to no longer be accepted.

@slightlyoutofphase
Copy link
Contributor

slightlyoutofphase commented Mar 24, 2021

The problem is that the code posted above is accepted on stable, but the intention of the const_trait_impl feature is that all trait bounds on const fns will require an impl const Trait, unless you use T: ?const Trait syntax to opt out, which would cause the code to no longer be accepted.

This makes much more sense if they're specifically talking about stable, where literally no feature flags can be used (meaning const_trait_impl and numerous other things don't actually exist there currently). They stated that they're using nightly in the initial comment, though, which is what I was going off of.

Edit: Actually, I'm not sure the "blanket requirement" interpretation of what is meant in the RFC is entirely correct. The way it works now, if actually using const_trait_impl, is like this:

#![allow(incomplete_features, dead_code)]
#![feature(const_fn, const_trait_impl)]

pub trait Const {
    const ONE: usize = 1usize;
    fn one() -> Self;
}

impl const Const for u8 {
    fn one() -> u8 {
        1u8
    }
}

impl Const for usize {
    fn one() -> usize {
        1usize
    }
}

const fn foo1<T: Const>() -> usize {
    T::ONE
}

const fn foo2<T: Const>() -> T {
    T::one()
}

fn main() {
    // `foo1` explicitly returns `1usize` no matter what, so the `constness` of T's
    // `Const` trait impl is completely irrelevant.
    const X: usize = foo1::<u8>();
    const Y: usize = foo1::<usize>();
    // `u8` did `impl const Const`, so the next line works with `foo2`.
    const Z: u8 = foo2::<u8>();
    // `usize` just did `impl Const`, so the next line does not work with `foo2`.
    const W: usize = foo2::<usize>();
}

Playground link.

Which is to say, in the context of a const fn that neither explicitly "opts-in" or "opts-out" to / of a trait bound specifically needing to be implemented as const by T, the constness of the impl is irrelevant unless the code actually uses it in a way that makes it relevant (i.e. by calling an actual function, as opposed to using an integral constant that is, well, an integral constant).

The only requirements for associated anything the RFC mentions is the requirement for associated types to also have const impls while actually implementing a given trait as const.

@rodrimati1992
Copy link
Contributor Author

rodrimati1992 commented Mar 24, 2021

@slightlyoutofphase The code isn't using any features at all(so it shouldn't matter that my initial comment used nightly), and yes this works on stable in every version I tried back to Rust 1.31.0 https://rust.godbolt.org/z/7TnxfPP3W

The only reason I mentioned that I'm using nightly is to make it obvious that this wasn't fixed by a recent PR.

Please don't assume that using nightly means that I must want to use any or all nightly features.

@slightlyoutofphase
Copy link
Contributor

@slightlyoutofphase The code isn't using any features at all(so it shouldn't matter that my initial comment used nightly), and yes this works on stable in every version I tried back to Rust 1.31.0 https://rust.godbolt.org/z/7TnxfPP3W

I still don't understand how there's any kind of "forward compatibility" concern. You cannot write const fn code on stable that relies on anything related to const_trait_impl in a way that could be broken by the compiler in the future, because you cannot actually implement traits as const on stable or call the methods of any trait in a const fn on stable.

Your original example could only be broken if the maintainer of the Wrapper crate went ahead and changed the associated C constant to something completely different, which would be a breaking change entirely on their part that isn't really related to const_fn or const_trait_impl at all. An associated constant u8 is just like any other valid constant in Rust.

@rodrimati1992
Copy link
Contributor Author

rodrimati1992 commented Mar 25, 2021

I still don't understand how there's any kind of "forward compatibility" concern.

SImple, the code compiles now, and it'll break whenever const impls and bounds are fully implemented.

It''ll be required to be a const Const impl in the const D: u8 = foo::<u8>(); line, and the error will be "Expected implementation of const Const for Wrapper<u8>, found (non-const)Const impl"

I repeat, no code at all in #83452 (comment) needs to change for that to break.

Also, there is no code around that, it's the only code you need in a main.rs file in a project that only depends on std, only the compiler changes here, and no nightly features at all.

@slightlyoutofphase
Copy link
Contributor

slightlyoutofphase commented Mar 25, 2021

It''ll be required to be a const Const impl in the const D: u8 = foo::<u8>(); line, and the error will be "Expected implementation of const Const for Wrapper<u8>, found (non-const)Const impl"

Again, my point is that it's not clear to me that this is actually the case for something that strictly involves nameable integral constants with known primitive types. It doesn't make any sense, and has none of the same actual stability concerns that calling const trait methods (which you cannot do currently on stable) does.

Consider something like the following, also, that does work on stable today as well:

trait Trait {
    const F: usize = 99;
}

struct X;

impl Trait for X {}

const fn foo() -> usize {
    X::F
}

What it actually does is ultimately the same thing as your example. It's just accessing the constant indirectly. Which is fine, because if someone changes F or removes F from Trait entirely it'd be equally as breaking of a change in both const and runtime contexts.

Look at the playground link I posted earlier, also. The way it's been made to work so far is logical and provides really good error reporting. Intentionally "dumbing it down" in the particular way you're suggesting would be very strange IMO.

@rodrimati1992
Copy link
Contributor Author

Intentionally "dumbing it down" in the particular way you're suggesting would be very strange IMO.

No, it'd make the most sense, because you don't have to look into the function or the trait definitions to figure out whether a const impl is requried. What you're asking is that const bounds are inferred from the function definition.

@slightlyoutofphase
Copy link
Contributor

No, it'd make the most sense, because you don't have to look into the function or the trait definitions to figure out whether a const impl is requried. What you're asking is that const bounds are inferred from the function definition.

Who doesn't have to "look into" what, exactly, in what specific context? I don't understand what you mean.

@rodrimati1992
Copy link
Contributor Author

rodrimati1992 commented Mar 25, 2021

No, it'd make the most sense, because you don't have to look into the function or the trait definitions to figure out whether a const impl is requried. What you're asking is that const bounds are inferred from the function definition.

Who doesn't have to "look into" what, exactly, in what specific context? I don't understand what you mean.

Imagine that function definitions are opaque(like they are WRT trait bounds, you can't add trait bounds inside the function body), what is the constness of the bounds of the function?

@slightlyoutofphase
Copy link
Contributor

Can you give an example of what you're referring to?

@rodrimati1992
Copy link
Contributor Author

rodrimati1992 commented Mar 25, 2021

This gives you an error, it doesn't implicitly add a Clone bound to the function

fn clone_something<T>(x: &T) -> T {
     <T as std::clone::Clone>::clone(x)
}

what you're proposing is analogous to inferring the T: Clone bound from the code inside the function definition (which is { <T as std::clone::Clone>::clone(x) }) in clone_something and erroring at the call site, ie:

fn clone_something<T>(x: &T) -> T {
     <T as std::clone::Clone>::clone(x)
}

{
    struct Unclonable;
    clone_something(&Unclonable); // Erroring at this line. If it was removed, there'd be no error
}

@slightlyoutofphase
Copy link
Contributor

slightlyoutofphase commented Mar 25, 2021

I'm not "proposing" anything at all. I've continuously just tried to make the point that the only possible way to use traits in const fn on stable currently is a way that is unrelated to and unimpacted by const_trait_impl (that is, by using numeric associated constants).

Also, I don't think your example is all that similar to something to something like:

#![allow(incomplete_features, unused_variables, dead_code)]
#![feature(const_fn, const_trait_impl)]

use std::ops::Add;

#[derive(Copy, Clone)]
struct NotConstAdd {}

impl Add for NotConstAdd {
    type Output = Self;

    fn add(self, _other: Self) -> Self::Output {
        self
    }
}

const fn do_add<T: Copy + Add<Output = T>>(a: T) -> T {
    // Don't use Add
    a
}

const fn really_do_add<T: Copy + Add<Output = T>>(a: T) -> T {
    // Do use Add
    a + a
}

// Works.
const A: NotConstAdd = do_add(NotConstAdd {});
// Does not work.
const B: NotConstAdd = really_do_add(NotConstAdd {});

fn main() {
    // Both of these work, because they're not being called in a const context.
    do_add(NotConstAdd {});
    really_do_add(NotConstAdd {});
}

Again, the only thing that's missing in the above is the ability to "opt out" / "opt in" to the constness of the impl, which just isn't implemented yet. The error given for B is extremely explicit about what's going wrong though even as is. I don't feel like anything is being made more confusing / being hidden at all.

Edit: You also have to consider that const_trait_impl enables purely syntactic things like the following, for which an external function bound isn't relevant at all, and which have to be useable in both const and runtime contexts:

#![allow(incomplete_features)]
#![feature(const_fn, const_trait_impl)]

use std::ops::Deref;

struct Strange {}

impl const Deref for Strange {
    type Target = str;

    fn deref(&self) -> &Self::Target {
        "What a weird way to use deref!"
    }
}

const STR: &str = &*Strange {};

fn main() {
    println!("{}", STR);
}

@rodrimati1992
Copy link
Contributor Author

rodrimati1992 commented Mar 25, 2021

@slightlyoutofphase
You don't seem to understand that Rust doesn't change what functions require of callers(at the type level) just because the body of the function changed(and if it happens it's a language limitation, not an intentional feature), which is what you keep proposing should be kept about the experimental feature flag you're using (#![feature(const_trait_impl)]).

I've continuously just tried to make the point that the only possible way to use traits in const fn on stable currently is a way that is unrelated to and unimpacted by const_trait_impl (that is, by using numeric associated constants).

I agree with that, so long as we're talking about cases that don't require trait bounds being added to the function. The example in my first comment is wrong because of the trait bound.

Here is a case that isn't problematic:

trait Foo {
    const X: u8;
}

struct Generic<T>(T);

impl<T> Foo for Generic<T> {
    const X: u8 = 10;
}

const fn hello<T>() -> u8 {
    Generic::<T>::X
}

The reason this is fine is because no bound needs to be added to hello either explicitly or implicitly.

@slightlyoutofphase
Copy link
Contributor

You don't seem to understand that Rust doesn't change what functions require of callers(at the type level) just because the body of the function changed.

I understand that perfectly, just as I understand these are experimental features and everything else. I don't agree it's an accurate description of what we're talking about here though, particularly in the presence of stuff like the language-level syntactic use cases for const_trait_impl such as the Deref example I included above.

Your hello example to me has just as many standard ways of going wrong as anything else, const or not, though. It uses Generic internally, which still isn't "known" at all as a caller if you haven't read the source, and if the person who is writing the hello function is not the same person who maintains Generic then Generic could itself be altered at any time such that the way it's being used in hello no longer compiles.

@rodrimati1992
Copy link
Contributor Author

rodrimati1992 commented Mar 25, 2021

@slightlyoutofphase Your const Deref example is not incompatible with anything I said.

Again, let's not use examples of language limitations (eg: not being able to prove that the code that initializes associated constants terminates), as a reason to make bounds implicitly const depending on code inside the function.

@slightlyoutofphase
Copy link
Contributor

@slightlyoutofphase Your const Deref example is not incompatible with anything I said.

I meant it as an example of something that technically presents the same scenario you're describing as being the overall issue here, where any struct at all may or may not be validly dereferenceable with * in a const context. So as a "caller" of * you either have to rely on documentation, or "just try it".

eg: not being able to prove that the code that initializes associated constants terminates

That certainly wasn't my point. And I still disagree that any bounds are being "changed" overall even currently. Everything we're talking about here is a matter of "is or is not an explicit compile-time error".

@rodrimati1992
Copy link
Contributor Author

rodrimati1992 commented Mar 25, 2021

Everything we're talking about here is a matter of "is or is not an explicit compile-time error".

What I'm talking about is exclusively about whether a const fn requires an impl of a const Trait when called in a const context.

I'm saying that this is how it should work in generic contexts:

use std::ops::Add;

const fn foo<T: Add>() {
    // The function definition doesn't matter WRT its bounds, so I'm not writing it.
}

const fn bar<T: ?const Add> (l: T, r: T) {
    foo::<T>()
}

which should eventually error at the foo::<T>() line when bar is defined, with this error:

error[E0277]: cannot add `T` to `T` in const contexts
 --> src/lib.rs:6:11
  |
1 | const fn foo<T: std::ops::Add>() {
  |                 ------------- required by this bound in `foo`
...
6 |     foo::<T>()
  |           ^ no implementation for `T + T` usable in const contexts
  |
help: consider changing the `?const Add` bound for the type parameter `T` to `Add`
  |
5 | const fn bar<T: Add> (l: T, r: T) {
  |               ^^^^^

bar should never need to be called for this error to happen.

That code above is analogous to this:

fn foo<T: std::ops::Add>() {
    // The function definition doesn't matter
}

fn bar<T> (l: T, r: T) {
    foo::<T>()
}

which errors at the foo::<T>() line when bar is defined:

error[E0277]: cannot add `T` to `T`
 --> src/lib.rs:6:11
  |
1 | fn foo<T: std::ops::Add>() {
  |           ------------- required by this bound in `foo`
...
6 |     foo::<T>()
  |           ^ no implementation for `T + T`
  |
help: consider restricting type parameter `T`
  |
5 | fn bar<T: Add> (l: T, r: T) {
  |         ^^^^^

bar never needs to be called for this error to happen.

@slightlyoutofphase
Copy link
Contributor

Yeah, that makes sense as far as how the opt-out should interact with everything else. I wasn't arguing that.

@rodrimati1992
Copy link
Contributor Author

rodrimati1992 commented Mar 25, 2021

Yeah, that makes sense as far as how the opt-out should interact with everything else. I wasn't arguing that.

How, this clearly looks like you're arguing that the required constness of bounds should be essentially compile-time duck typing, where the contents of the function determines what constness is required of the caller:

const fn do_add<T: Copy + Add<Output = T>>(a: T) -> T {
    // Don't use Add
    a
}

const fn really_do_add<T: Copy + Add<Output = T>>(a: T) -> T {
    // Do use Add
    a + a
}

// Works.
const A: NotConstAdd = do_add(NotConstAdd {});

// Does not work.
const B: NotConstAdd = really_do_add(NotConstAdd {});

My argument is that neither should work, because it makes is a breaking change to use + inside the function, even for a no-op (eg: if false { a + a }). Your argument is that it should be easier for code to break due to changes in implementation details, and I don't see a good motivation.

If you think that dynamically unreached code inside a function shouldn't trigger this error, it's even worse of an idea, and that's how it works now(because it's not enforced at the type level yet):
https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=a9cb9aa7049789fbb98f6513f5f34f32

const fn do_add<T: Copy + Add<Output = T>>(a: T) -> T {
    // Don't use Add
    a
}

const fn other_fn() -> bool {
    false
}

const fn really_do_add<T: Copy + Add<Output = T>>(a: T) -> T {
    let cond = other_fn();
    // Do use Add, just in non-obviously dead code.
    if cond { a + a; }
    a
}

// Works.
const A: NotConstAdd = do_add(NotConstAdd {});
// Also works.
const B: NotConstAdd = really_do_add(NotConstAdd {});

@rodrimati1992
Copy link
Contributor Author

rodrimati1992 commented Mar 25, 2021

eg: not being able to prove that the code that initializes associated constants terminates

That certainly wasn't my point.

What was your point then, the only thing that could break in Generic::<T>::X that would reach callers of hello is if there's a panic inside of the expression that initializes the X associated constant(which isn't your point). The alternatives all get triggered when hello is defined, without being called by anyone.

Everything we're talking about here is a matter of "is or is not an explicit compile-time error".

You're not getting that this is about what you can determine about a function without even running it or peeking inside, there would be no absolute requirement for trait bounds if we only just cared that things eventually error at compile-time, they'd just be a way to document what the function does. Identical to that, const fn foo<T: Bar> shouldn't care what happens in the body to determine what calls to it are errors WRT const trait impls.

I just don't get why you think that expressions inside of functions should determine what impls are required of callers, that's not how it works today (without using incomplete experimental features), and it's not how it should work in the future.

@slightlyoutofphase
Copy link
Contributor

slightlyoutofphase commented Mar 25, 2021

@jonas-schievink: thanks for the link, that's helpful info to have. I guess I'm still a bit unclear about how something that is not itself a trait impl, but rather a free function (or non-trait inherent method) is ultimately intended to behave, though. E.G:

// Say this is just a regular inherent struct method
pub const fn get_usize(&self) -> usize {
    NonSelfConcreteImplementerOfSomeTrait::USIZE
}

or for a more complex example:

trait Trait {
    const VALUE: usize = 999;
}

struct Struct {}

impl Trait for Struct {}

impl Struct {
    const fn get_usize(&self) -> usize {
        1
    }
}

// Does not refer to `Trait` externally at all.
const fn get_usize(s: &Struct) -> usize {
    // Technically, either side of the addition operation below could become
    // invalid code if the person who maintains `Struct` changes it, assuming
    // that's not the same person who maintains this `get_usize` free function.
    s.get_usize() + <Struct as Trait>::VALUE
}

const STRUCT: Struct = Struct {};

const U: usize = get_usize(&STRUCT);

fn main() {
    println!("{}", U);
}

@rodrimati1992:
Every single code example I've given in this entire thread before this comment was examples of things that currently produce very explicit, easy-to-understand compile-time errors if used incorrectly. Each time I said "does not work" in the comments, I meant "rustc errors on this while compiling it."

Why would I be talking about runtime errors as far as your hello example? I was talking about the numerous things that could be done to make hello also produce compile-time errors, such as X no longer being u8, or Generic no longer implementing Foo, and so on.

@rodrimati1992
Copy link
Contributor Author

rodrimati1992 commented Mar 25, 2021

Every single code example I've given in this entire thread was examples of things that currently produce very explicit, easy-to-understand compile-time errors if used incorrectly

That's missing my point entirely, it doesn't matter if it eventually errors, read my point about guarantees.

You're not getting that this is about what you can determine about a function without even running it or peeking inside, there would be no absolute requirement for trait bounds if we only just cared that things eventually error at compile-time, they'd just be a way to document what the function does. Identical to that, const fn foo<T: Bar> shouldn't care what happens in the body to determine what calls to it are errors WRT required const trait impls (required for the caller).

Also, bringing up examples of panicking const fns is completely irrelevant to my point about const bounds (and therefore impls required inside functions), because bounds are enforced at the call site whenever the call site is defined, not when the call site is eventually called itself, even if the functions are all generic.

such as X no longer being u8, or Generic no longer implementing Foo.

Obviously, that's already a breaking change for Generic to do, regardless of associated constant or function, and it'll be a compile-time error in hello before it's even called anywhere. Can you guess that I'm fine with that happening because it's not implicit in function definitions, but in the public API of Generic?
(the comment with the code we're talking about)

I don't get this one

Why do you keep bringing this example up as though it proves a point about const bounds?. If it's not about const bounds stop bringing it up, we're talking code that I argue should error because of const bounds, not about panicking const code.

// Say this is just a regular inherent struct method
pub const fn get_usize(self) -> usize {
    NonSelfConcreteImplementerOfSomeTrait::USIZE
}

This is the same as putting the associated constant in a local const X: usize = NonSelfConcreteImplementerOfSomeTrait::USIZE; inside the function, then using that.

And if you're using associated constants from a type parameter, <T as Trait>::FOO (where Foo is unconditionally implemented for all types) it's:

struct X<T>(T);

impl<T> X<T> {
    const GET_X: usize = <T as Trait>::FOO;
}

pub const fn get_usize(self) -> usize {
    X::<T>::GET_X
}

which clearly isn't problematic, no non-const trait impl is required inside the function.

@rodrimati1992
Copy link
Contributor Author

rodrimati1992 commented Mar 25, 2021

If you keep saying that it (the enforcement of const impls) happens to work now, you are making the mistake of assuming that how it works now is likely what will go to stable, so obviously it's not a bug that the code in my original comment is accepted(it is a bug).

@rodrimati1992
Copy link
Contributor Author

You might think you're making a point by showing const fns that panic, you're not, all you're doing is showing limitations in Rust that should be avoided (in the design of the language) whenever there's a good alternative.

@rodrimati1992
Copy link
Contributor Author

rodrimati1992 commented Sep 2, 2022

This is intentionally allowed now, so no point in leaving this issue open.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-const-eval Area: Constant evaluation, covers all const contexts (static, const fn, ...) A-stability Area: `#[stable]`, `#[unstable]` etc. C-bug Category: This is a bug. F-const_trait_impl `#![feature(const_trait_impl)]`
Projects
None yet
Development

No branches or pull requests

3 participants