rust-lang/rust

Meta tracking issue for `const fn`

Lokathor opened this issue ยท 95 comments

This issue tracks the progress of const fn as well as const evaluation more broadly.

This issue is not for discussion about specific extensions to const fn or const evaluation and only exists to provide links to other places that track the progress of specific issues. If you wish to discuss some subject related to const evaluation or const fn, please find an existing appropriate issue below or create an new issue and comment here with a link to the newly created issue.

If it's impossible to detect what issue the programmer should be pointed to, they can simply be pointed to this issue.


The const related issues currently on deck are as follows:

  • Control flow: #49146 (1.46)
  • Loops: #52000 (1.46)
  • Integer arithmetic: #53718
  • Integration with patterns: #57240
  • Floating point arithmetic: #57241
  • Panics: #51999 (#![feature(const_err)])
  • Comparing raw pointers: #53020
  • Dereferencing raw pointers: #51911
  • Raw pointer to usize casts: #51910(#![feature(const_raw_ptr_to_usize_cast)])
  • Union field accesses: #51909
  • &mut T references and borrows: #57349 (#![feature(const_mut_refs)])
  • FromStr: #59133
  • transmute: #53605
  • Function pointers (const fn(...) / fn(...)): #63997
  • const unsafe? extern fn: #64926
  • unsizing casts: #64992
  • platform intrinsics: #66061
  • async blocks in const fns: #85368
  • mem::discriminant: #69821(#![feature(const_discriminant)])
  • ptr offset methods #71499
  • Warnings on long running evaluations: #49980
  • Constant propagation not causing errors: #28238
  • const_maybe_uninit_assume_init #86722

Open RFCs:

Planned RFCs:


Completed:

  • const constructors: #61456
  • locals, assignments, and destructuring: #48821
  • unsafe operations: #55607

The rustc_const_unstable attribute should also contain an issue number, so each const feature can redirect to a specific tracking issue, rather than defaulting to #24111, which is now closed (which might be misleading to users looking for an active issue).

As a rough starting point, you can search for occurrences of "rustc_const_unstable" in the compiler to figure out how the attributes are being handled. This will require changing the diagnostics for rustc_const_unstable to point to specific tracking issues. I can provide more detailed mentoring instructions if someone wants to tackle this.

Would it be appropriate to turn those bullet-points into checkboxes?

Also, should there be something in the list about using traits in const fn contexts? It looks like #2237 was closed without making much headway.

I changed the word "still" to "currently" when giving the list.

Given your example there of a thing that needs to be considered but doesn't have an issue, I don't think that we should use a checkbox system. That implies a finality if we do check off all the boxes, but the work will probably be ongoing even then. Probably simpler to add and remove from the list as things are opened and closed.

...I don't know, I think that if the list had value 11 days ago, it will continue to have value as const fn issues are opened or closed.

I think that some sort of list that tracks what's already done with const should reside elsewhere, but we can have check boxes I guess.

@BatmanAoD I assume you meant #53972?

Also, should there be something in the list about using traits in const fn contexts?

That is still being discussed in a pre-RFC: rust-lang/const-eval#8

Once an RFC has been opened and merged, there'll be a tracking issue on this repo that we can link to from this issue

@tormol Nope, I hadn't run across that! Thanks for pointing me to it.

What about a string->int conversions?
I think doing a env!("SOMETHING").parse().unwrap() is a wanted use case to be evaluated at compile time

Sounds totally reasonable to eventually have.

Make an issue and it can go on the list.

Are there tracking issues covering the issues in this example? cc @oli-obk

fn foo() -> &'static [u8] {
    // ok
    &[0, 1, 2, 3]
}

const fn bar() -> &'static [u8] {
    // error[E0723]: unsizing casts are not allowed in const fn (see issue #57563)
    // warning[E0515]: cannot return reference to temporary value
    &[0, 1, 2, 3]
}

playground link

Resolving the error seems like a straightforward extension. I don't think unsizing casts would draw much controversy or require much implementation effort.

The warning requires a touch more explanation. Specifically, the non-const version works because of rvalue static promotion and I believe the const code should do the same. This means the array is never a temporary on the stack in the first place.

EDIT: This has been resolved: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=5968ed38cb23d180f5842d9a0ba9b3e0

How about allowing traits like Add and Sub to be const as well?

I remember hearing about some talk for trait implementations to be able to be declared as a const implementation in the Discord, but I don't have the tracking number for that issue, and I don't even know if that's in an issue in the first place.

So, it's on someone's mind at least, and if you find it please comment here and I'll add it into the list.

Usage of traits in const context is a currently active RFC: rust-lang/rfcs#2632

Is there a specific tracking issue for const TypeId?

For some reason, making a const fn that returns Vec::new() doesn't result in the same assembly as creating the Vec::new() as a const item:

#![feature(const_vec_new)]

const A: Vec<usize> = Vec::new();

pub const fn return_empty_vec() -> Vec<usize> { Vec::new() }

pub const fn return_vec_via_const_item() -> Vec<usize> { A }

Theoretically, both return_empty_vec and return_vec_via_const_item should result in the same assembly, but the assembly of return_empty_vec is a lot worse: https://rust.godbolt.org/z/pTl7Eo

Both functions should technically result in this assembly:

example::return_vec_via_const_item:
        mov     rax, rdi
        mov     rcx, qword ptr [rip + .L__unnamed_2]
        mov     qword ptr [rdi], rcx
        mov     rcx, qword ptr [rip + .L__unnamed_2+8]
        mov     qword ptr [rdi + 8], rcx
        mov     rcx, qword ptr [rip + .L__unnamed_2+16]
        mov     qword ptr [rdi + 16], rcx
        ret

.L__unnamed_2:
        .asciz  "\b\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"

... but only return_vec_via_const_item does.

However, when using -O, now suddenly return_empty_vec is better:

example::return_empty_vec:
        mov     rax, rdi
        mov     qword ptr [rdi], 8
        xorps   xmm0, xmm0
        movups  xmmword ptr [rdi + 8], xmm0
        ret

Ideally, it would be great if this would be also the case for debug builds.

@fschutt I'm not sure this is actionable. I'd expect that a debug build does not inline function calls, so I'm assuming that return_empty_vec will in fact contain a function call to Vec::new(). Making a function const fn does not mean calling it in a regular function will const evaluate the function. If you want to guarantee that const evaluation happens, use an explicit constant (as you did).

All const fn guarantees is that you can call it at compile-time, not that it is automatically processed at compile-time at every use site.

@fschutt I don't believe debug builds are under any obligation to perform any optimization.

Using -O makes the assembly for return_empty_vec extremely simple: https://rust.godbolt.org/z/M8CcA3

Yes, obviously there's no "obligation" for rustc to do it, but "it would be nice" because rustcs runtime performance in debug builds is already sub-par. I usually build with -C opt-level=1 because at -C opt-level=0 my code runs at about 3fps (which is not a problem of my code, it's just that the performance of the debug-mode assembly is so extremely slow). At -C opt-level=1 I'd expect Rust to inline const fn and very small functions, but it doesn't do that: https://rust.godbolt.org/z/tWb6bU

So now I basically have to write something like:

#[cfg(debug_assertions)] { A } // to get better debug-runtime perf
#[cfg(not(debug_assertions))] { Vec::new() } // to get the best release mode assembly

Inlining and pre-computing the contents of a const fn even in debug builds would be nice to have in order to improve the runtime performance in debug builds. There is not really a reason for a constant, pure function not to be precomputed. There is a 20x to 100x discrepancy between debug and release builds, which isn't a good thing because you either have to take the insanely long compile times of release mode or the 3fps code in debug mode (or --opt-level=1 code). While I'm no expert on this topic, it shouldn't take that much compile time to evaluate a const fn. Maybe I should do more benchmarks to prove my point.

This is entirely orthogonal to const fn imo. You're talking about optimizations that can happen to any kind of expression (e.g. 1+2 being 3). Please open a separate issue, so we can talk about which optimizations should happen in debug mode.

@fschutt please ping me in said issue if you make it because I strongly agree with you. (oli is correct though, it doesn't go here)

The issue of what optimizations should occur in debug mode actually seems fairly open-ended to me (and probably not really even Rust-specific), so it may be more appropriate as a discussion topic on http://internals.rust-lang.org/ rather than here on GitHub.

I do agree that debug mode performance is important, and I appreciate your specific example of an unusable framerate. I'm not sure inlining is necessarily the answer, though. (I would guess that LLVM emits enough debug info to support stack traces through inline functions, but I don't actually know so.)

What's the status on const_str_as_bytes? It seems like this should be easily stabilized, as it's currently just a simple field access (source code).

You have looked at the String::as_bytes method, that feature gate is about str::as_bytes, which has a less trivial body:

rust/src/libcore/str/mod.rs

Lines 2137 to 2144 in 00859e3

#[rustc_const_unstable(feature="const_str_as_bytes")]
pub const fn as_bytes(&self) -> &[u8] {
union Slices<'a> {
str: &'a str,
slice: &'a [u8],
}
unsafe { Slices { str: self }.slice }
}

We have to figure out our transmute and union field access story before we can stabilize that method

Is there a more specific tracking issue for function pointers in const fns? e.g. https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=5455f0bdf725924403901eba36cdd58a

There's the const bounds RFC which has a section for function pointers in the future work part: https://github.com/oli-obk/rfcs/blob/const_generic_const_fn_bounds/text/0000-const-generic-const-fn-bounds.md#const-function-pointers

Are there any plans to support const fn usage of OsStr::new? It would be nice to be able to create OsStr constants from string literals for things like filenames (see also Path::new)

Yes, but that requires RFC 2632 rust-lang/rfcs#2632 in order to be able to have stable const fn with trait bounds on their generic parameters

What about supporting trait bounds like the following?

trait Foo {
    const INIT: Self;
}
struct Bar<T: Foo>(T);
impl<T: Foo> Bar<T> {
    const fn new() -> Self {
        Bar(T::INIT)
    }
}

They shouldn't require a RFC AIUI.
A real-life example of those is in parking_lot::lock_api, and it would be great if that could be done with stable.

Yeah, it's unfortunate that we can't use associated consts and marker trait bounds before the rest of it.

marker trait bounds

Or any other trait bound that is not relevant to the const fn, for that matter. e.g.

trait Foo {}
struct Bar<T: Foo>(PhantomData<T>);
impl<T: Foo> Bar<T> {
    const fn new() -> Self {
        Bar(PhantomData)
    }
}

(although, a marker is involved, but it's not in the trait bound itself)

Perhaps a way to define this is: if a const fn has a trait bound (perhaps inherited from an impl block) but does not involve any of the items of that trait (methods, associated types, โ€ฆ), then it should be allowed. (The bound can still be relied on to satisfy other bounds, e.g. those on a struct definition when constructing a value of that struct type.)

plus traits with associated consts (with no involvement from any other items of that trait)

While we could just allow const fns that don't use generics or don't actually use them to call functions, there are many questions about forward compatibility both in the language and in user defined const fn. This topic is discussed on the RFC in detail.

The problem is not having trait bounds (so knowing that T: Foo), it is declaring them (so the writing out of the T: Foo) and what the semantics of declaring them are.

AFAICT, the RFC doesn't talk about associated consts and traits with no items involved. And I don't see what forward compatibility issues those could lead to.

TLDR:

const fn foo<T: Trait>() -> i32 { T::SomeAssocConst }

is fine

const fn foo<T: Trait>() -> i32 { T::some_fn() }

is not and the trait bounds are exactly the same. Also you don't want the body contents to have an effect on what types you are allowed to use as T (some types may have some_fn be const, so be ok, some may not have that). The RFC solves this question in a consistent manner.

Long version:

The RFC doesn't have to talk about associated consts, because as you correctly inferred, associated consts are completely unproblematic. We could totally add trait bounds and allow you to use associated consts. The problem is that adding trait bounds would also give you access to associated functions, and then the question comes up whether you are allowed to call them. The RFC answers this question. Adding trait bounds to const fns needs to answer all questions about the items on the traits at once, we can't separate these out. The RFC also explains why we need to clear up this question and not invent a new scheme for making functions callable later. If the RFC is unclear (or even just the motivation or summary section), please leave comments on the RFC so I can make that clearer.

More information on how we got here can be found in #53972 and rust-lang/const-eval#1

The problem is that adding trait bounds would also give you access to associated functions

Does it have to? There are number of things that currently can't be used in const functions, despite being part of the language, like match (IIRC, it's still not allowed). I can totally get that the compiler implementation to allow trait bounds only for non-problematic cases might be non-trivial (and I have no clue, it might as well be easy), but I'd argue that's an implementation detail.

I haven't used it in a while, but I recall that the const_fn feature on nightly already lets you use trait bounds in const fn. There's no methods callable because none are const, but it already allows "just marker traits" (and maybe associated consts?) perfectly fine.

Does it have to?

No, but once we allow trait bounds on the generic parameters of a function, and we allow you to call that without having a const impl for the trait, we lock in into that system and can't ever go back. This is all explained in the RFC and linked discussions. If it weren't a forward compat issue you'd have trait bounds already. The RFC was created because we've had this exact discussion a year ago.

I haven't used it in a while, but I recall that the const_fn feature on nightly already lets you use trait bounds in const fn. There's no methods callable because none are const, but it already allows "just marker traits" (and maybe associated consts?) perfectly fine.

Yes, that's right. The RFC reexposes this feature via const fn foo<T: ?const Trait>() that doesn't require a const impl for the Trait for the types passed to the function.

I would not create an RFC if it were unnecessary. I want these features as much as everyone else. The last const fn issue ended up having a few hundred comments going back and forth on this topic. Please take the discussion to the RFC, this issue is not meant for it. Or create a new issue that we can link from here so we stop polluting the meta issue.

Or create a new issue that we can link from here so we stop polluting the meta issue.

๐Ÿ‘ -- This issue is not for length discussion about a specific issue. If you want to discuss a topic in-depth, please create a new issue and link it here in a comment or use one of the existing linked issues at the top.

mexus commented

Seems like the last item "Const constructors: #61456" has been already implemented in #61209 (as per Centril's comment #61456 (comment))

Is there an issue for const fn types ?

I expected this to work:

const fn foo() {}
const FOO: const fn() = foo;
const fn bar() { FOO() }

but it fails due to multiple issues:

  • function types cannot be called from const fns
  • const fn() errors with const is a keyword

Is there a tracking issue for const transmute? It's missing from the list and I couldn't find it in the issue tracker either?

Is there an issue for const fn types ?

I think that's part of the discussion around const fn with (full) generics: rust-lang/rfcs#2632.

@RalfJung #53605 (adding to the list).

@gnzlbg Please file an issue if you believe one is lacking.

Done: #63997

Would it be possible to special-case bool && bool, bool || bool, etc.? They can currently be performed in a const fn, but doing so requires bitwise operators, which is sometimes unwanted.

@jhpratt Please move this to the relevant tracking issue (#49146).

What about const async,unsafe pointer arihmetics, const like a type modifier(returning const function from another one)?

#64926 shoule be added to this tracking issue

nvzqz commented

What's the status on const_str_len? I would be happy to contribute to its stabilization. However, I don't know where to find the next steps for that. Asking here since the unstable book links to this issue.

Nevermind, it's been stabilized for 1.39 at #63770.

OsStr::new would be cool.

Over last months functionality of constant functions has greatly improved. What is a current state of const functions in traits and use of generic parameters?

OsStr::new would be cool.

That would require making AsRef const fn, which first requires having const functions in traits (see the RFC https://github.com/rust-lang/rfcs/blob/master/text/0911-const-fn.md)

@Lokathor Please add #66806 :)

What's the reason for not allowing trait bounds to be in scope of const fn at all? I understand not allowing method calls based on the traits, but not allowing any trait bounds other than Sized requires very ugly workarounds at times.

I almost feel like this is rejected by accident given the error message: "trait bounds other than Sized on const fn parameters are unstable". Is the check too eager here, since this also triggers on const fn that doesn't have any parameters? Or is it talking about type parameters specifically, even if the const fn doesn't make use of them?

Trait bounds can still be used/enforced in const/static context, leading to workarounds such as this:

struct Outer<T: PartialEq> {
    inner: Inner<T>,
}

struct Inner<T> {
    _t: Option<T>,
}

impl<T: PartialEq> Outer<T> {
    fn new() -> Self {
        Self {
            inner: Inner { _t: None, }
        }
    }
}

impl<T> Inner<T> {
    const fn new() -> Self {
        Self {
            _t: None,
        }
    }
}

struct NoCmp;

static S: Outer<NoCmp> = Outer { inner: Inner::new() };

Yes this is an unfortunate limitation right now. The reason is that if we permitted this now, we may run into backwards compatibility issues when we try to permit calling methods of generic parameters in const fn later. There's an RFC that will permit both const fn with generic parameters on which to call methods and const fn with generic parameters that may not be used in the const fn: rust-lang/rfcs#2632

Is the check too eager here, since this also triggers on const fn that doesn't have any parameters? Or is it talking about type parameters specifically, even if the const fn doesn't make use of them?

Any trait bounds on type parameters, even those on the type are available inside the const fn and would thus cause the backwards compat issues.

CryZe commented

Are Vec::new() and co. (so the data structure constructors that don't need any allocation) being tracked anywhere here? If not, I'd like to see those being const fn. Especially with parking_lot, spin and co. having const fn Mutex::new(...), it would be great to be able to create the data structures right away at compile time as well.

being tracked anywhere

Many of these are already stable, so you'll need to be more specific.

const V: Vec<u8> = Vec::new();
const S: String = String::new();

This issue is not for discussion about specific extensions to const fn or const evaluation and only exists to provide links to other places that track the progress of specific issues. If you wish to discuss some subject related to const evaluation or const fn, please find an existing appropriate issue below or create an new issue and comment here with a link to the newly created issue.

Hi,please add #69345.

#69345 really is part of rust-lang/const-eval#20 -- I am not sure if it makes any sense to track individual (not even unstably const yet) heap-related functions before we have a general plan for heap allocations.

Also, shouldn't tracking issues only exist for things that are already unstably implemented, and are just waiting for stabilization?

Tracking issues also exist for accepted RFCs, which doesn't exist yet for const heap related things. So yes, I think we should close all issues around const heap by pointing to rust-lang/const-eval#20

Is Box::new tracked? Is it going to be ever possible at all?

Box::new and generally any kind of heap allocation in const-eval is in pre-pre-RFC stage at rust-lang/const-eval#20.

Please add #71499

Which issue blocks const ptr::copy_nonoverlapping?

That would be at least #57349 and #51911.

@Lokathor Can we add const_caller_location to the list in the original comment?

Are there any plans/issues for slicing syntax? (i.e. &slice[..5])
Currently compiler refers to this meta issue, but I do not seem any related issue in list

@DoumanAsh that would fall under trait implementations.

I've been trying to find a tracking issue for defining const fns within traits.

say something like so

trait SomeTrait {
    const fn some_method();
}

Or has this not yet been suggested within an rfc?

Would it be helpful to have a sub-tracking issue related to const-ifying eligible stdlib functions? Would this be something a Rust newbie could perhaps work on? If so I'm interested.

I don't believe that there is an issue for the concept in general. I think that you just put in the PR for a function to become nightly const, and then later if it seems good it can be made stable const with sign off by T-libs.

There are these, which are a bit specific:

nvzqz commented

I noticed there's no tracking issue for CStr::from_bytes_with_nul_unchecked, which is feature gated behind const_cstr_unchecked. I would like to make use of this to safely create associated constants for a #[repr(transparent)] wrapper around CStr.

For example, the following would be safe:

#[repr(transparent)]
pub struct MyCStr(CStr);

impl MyCStr {
    pub const VALUE: &'static MyCStr = unsafe {
        let cstr = CStr::from_bytes_with_nul_unchecked(b"value\0");
        &*(cstr as *const CStr as *const MyCStr)
    };
}

Currently this is only possible by doing:

impl MyCStr {
    pub const VALUE: &'static MyCStr = unsafe {
        let cstr: &[u8] = b"value\0";
        &*(cstr as *const [u8] as *const MyCStr)
    };
}

However I'm not technically allowed to do this outside of std/core.

That function currently cannot be stabilized since it uses unsafe code. Thus it is blocked by rust-lang/rfcs#3016.

I know there is const_fn_traint_bound but I'm wondering why does it affect code inside cosnt fn?

For example:

///Windows thread pool timer
pub struct Timer {
    inner: AtomicPtr<ffi::c_void>,
    data: Cell<Option<Box<dyn FnMut()>>>,
}

impl Timer {
    #[inline]
    ///Creates new uninitialized instance.
    ///
    ///In order to use it one must call `init`.
    pub const unsafe fn uninit() -> Self {
        Self {
            inner: AtomicPtr::new(ptr::null_mut()),
            data: Cell::new(None),
        }
    }

Neither timer or uninit has trait bound.
but you get error on Cell, probably due to it having implicit dyn T

error[E0723]: trait bounds other than `Sized` on const fn parameters are unstable
   --> src\timer\win32.rs:118:19
    |
118 |             data: Cell::new(None),
    |                   ^^^^^^^^^^^^^^^
    |
    = note: see issue #57563 <https://github.com/rust-lang/rust/issues/57563> for more information
    = help: add `#![feature(const_fn)]` to the crate attributes to enable

But error message itself is not correct, and I'm not sure if it is not oversight in current const fn implementation.
Thoughts?

None here has type Option<Box<dyn FnMut()>>, and the "trait bounds" also refers to trait objects -- all types involving trait objects are rejected. The error could probably be made clearer.

The rejection comes out of an abundance of caution. In theory we could have a more precise analysis which realizes that None does not actually create a trait object. We need to be careful though since both creating a trait object and calling any method on it does need to be unstable.

Ok, I didn't know trait object are automatically denied even though these are still concrete types, despite being unsized.
I can work it around by storing trait object fat pointer as integer of correct size, so it is not so bad.

Forcing you to use integer-pointer-casts sounds pretty bad...

concrete types

The underlying concrete type behind dyn FnMut is not known, so in a sense these are not concrete types.

The underlying concrete type behind dyn FnMut is not known, so in a sense these are not concrete types.

This is true, but *mut dyn FnMut is known at compile time (it is always mem::size_of::<usize> * 2 which is why rejection is counter intuitive to me)
From that perspective it sounds pretty weird to consider it as trait bound.
But I doubt there is any consideration for that case so I only assume it will be fixed with stabilization of non-Sized trait bounds

rejection is counter intuitive to me

As I said, the true reason for rejection is that we do not want to stabilize creating or calling dyn Trait objects during CTFE time inside a const fn. This is done to remain future-compatible with many different options for rust-lang/rfcs#2632. For the same reason, function pointers are equally prohibited in const fn.

Your code should work, and I think a PR that carefully relaxes the const-checking analysis to accept such code would be accepted -- but @oli-obk might disagree.

Actually one would have to be even more careful than I thought -- this is deliberately rejected:

const fn foo(x: &mut dyn Trait) { ... }

This code is similar enough to const fn foo(x: impl Trait) that we want to reject both of them to avoid locking us into the wrong corner of the design space for trait bounds.

I don't think it should matter for compiler whether code is similar or not though.
&mut is concrete pointer known at compile type while impl Trait is dependent on invokation.
Btw I think both should be accepted (in long run at least)

I don't think it should matter for compiler whether code is similar or not though.

Two pieces of code that behave (almost) the same should be treated (almost) the same. It'd be very strange to accept one and reject the other.

Btw I think both should be accepted (in long run at least)

In the long run, they will be. That's what rust-lang/rfcs#2632 is about.

Do we have missing issue for MaybeUninit::assume_init?

In source code it is marked as const_maybe_uninit_assume_init

error: `MaybeUninit::<T>::assume_init` is not yet stable as a const fn

P.s. current API docs show it as const fn too despite it being unstable

That, I believe, was added in #79621. Do you want me to create a tracking issue?

Yeah, creating a tracking issue for the const_maybe_uninit_assume_init feature would make sense.

@DoumanAsh

P.s. current API docs show it as const fn too despite it being unstable

Nightly rustdoc hides the const before fn and displays (const: unstable) next to the version number (where "unstable" would link to a tracking issue if there was one). It will take a couple releases before this rustdoc feature makes it to stable docs.

Done, tracking issue #86722 created for const_maybe_uninit_assume_init

I am locking this issue. Please open individual issues or a zulip thread, and don't use this issue for discussion.