Stream: t-compiler/const-eval

Topic: trait bounds on const fn


oli (Dec 20 2019 at 09:50, on Zulip):

We can start experimenting with const fn that have trait bounds on their generic parameters

oli (Dec 20 2019 at 09:51, on Zulip):

see https://github.com/rust-lang/rfcs/pull/2632#issuecomment-567699174 for the T-lang post

oli (Dec 20 2019 at 09:52, on Zulip):

This does not mean the current design is endorsed or can be stabilized without the RFC actually finishing, but it allows gated experimentation so we can hammer out the last kinks in the RFC

oli (Dec 20 2019 at 09:53, on Zulip):

I propse that ?const Trait bounds are the first component of the RFC to get implemented

oli (Dec 20 2019 at 09:53, on Zulip):

I believe this would allow us to retire the const_fn feature gate once and for all, because there are no more features gated under it without the feature also having its own feature gate

oli (Dec 20 2019 at 09:54, on Zulip):

So basically on nightly, if you enable const_fn, you can declare trait bounds T: Trait on const fns, but you can't use them for anything but static dispatch to get associated constants and types (or methods, but not call said methods)

oli (Dec 20 2019 at 09:55, on Zulip):

This is exactly what ?const Trait bounds do as per the RFC: https://github.com/oli-obk/rfcs/blob/const_generic_const_fn_bounds/text/0000-const-generic-const-fn-bounds.md#const-opt-out

oli (Dec 20 2019 at 09:56, on Zulip):

After this change, using plain T: Trait bounds (other than Sized, which is stable) on const fns will become a hard error.

oli (Dec 20 2019 at 09:57, on Zulip):

As a parallel step, we can implement impl const Trait, requiring const fn checks for all methods on the impl.

oli (Dec 20 2019 at 09:58, on Zulip):

Following that we can implement logic to allow actually calling e.g. i32::add at compile-time

oli (Dec 20 2019 at 09:59, on Zulip):

and then as a final step we implement calling methods on generic parametes in const fns

oli (Dec 20 2019 at 09:59, on Zulip):

These four steps should be separate PRs and may even be split up further to separate the parser changes from the logic changes

oli (Dec 20 2019 at 10:07, on Zulip):

I have started a hack.md at https://hackmd.io/@oli-obk/generic_trait_bounds_on_const_fn

oli (Dec 20 2019 at 10:08, on Zulip):

Lets do an experiment and try to keep this document up to date with our plan

oli (Dec 20 2019 at 10:09, on Zulip):

so we can always discuss here or on the hackmd comments, but then lift the result of these discussions back to the hackmd content

lqd (Dec 20 2019 at 10:12, on Zulip):

do you want to start with T: ?const Trait because it's "not yet universally clear if it's a good idea" and validate/reject that hypothesis quickly ?

oli (Dec 20 2019 at 10:17, on Zulip):

no, because we want some scheme like that, and we already do have that scheme on nightly essentially

oli (Dec 20 2019 at 10:18, on Zulip):

If we take away this scheme without a replacement, that will cause lots of churn (likely we can't boostrap anymore)

lqd (Dec 20 2019 at 10:20, on Zulip):

hum ok, interesting, thanks oli

oli (Dec 20 2019 at 10:20, on Zulip):

It's the only proposed syntax that I know of, so we may as well use it even if it's a controversial feature

oli (Dec 20 2019 at 10:21, on Zulip):

the feature is already implemented, we just need to change its syntax. We could use an attribute for it, but that seems not strictly better

oli (Dec 20 2019 at 10:22, on Zulip):

This gave me an idea. I updated the hackmd to reflect that we should just add this syntax and wait for a boostrap bump. That way we can change the syntax with zero churn

oli (Dec 20 2019 at 10:22, on Zulip):

we could even add a lint to give users the time between the syntax addition and the boostrap bump to migrate

centril (Dec 25 2019 at 03:18, on Zulip):

I don't think lints for nightly migration is necessary

centril (Dec 25 2019 at 03:18, on Zulip):

and I'd rather avoid the technical debt it comes with

centril (Dec 25 2019 at 03:20, on Zulip):

Also, I'd like to see allow_internal_unstable(...) not work for this

centril (Dec 25 2019 at 03:20, on Zulip):

(and it should not be possible to smuggle generics in even via intermediary associated constants)

centril (Dec 25 2019 at 03:21, on Zulip):

Also, any syntax we add should be pre-expansion gated

ecstatic-morse (Jan 03 2020 at 01:06, on Zulip):

I implemented the syntax in #67820. I think I got the pre-expansion gating right. This PR has both the ?const syntax and the impl const Trait, since they both end up in a single field inTraitRef. I could split them up without too much effort, but I didn't see the comment above about separating the two syntax PRs before it was too late.

ecstatic-morse (Jan 03 2020 at 01:07, on Zulip):

Still coming out of a holiday-induced torpor.

ecstatic-morse (Jan 12 2020 at 12:27, on Zulip):

@oli Adding a field to Predicate::Trait doesn't increase its size, and doing that feels better than adding a field for all TraitRefs. By the time something appears in a Predicate, we need to know if the trait impl is const or not, not just if ?const appears on the bounds (I think you were alluding to this in your GH comment). Changing how ?const is stored in the AST/HIR could make this easier.

Specifically, I want ?const to be a TraitBoundModifier variant and impl const to be a field on ItemKind::Impl. I didn't do this in #67820 since ItemKind::Impl is matched exhaustively in a lot of places, which makes changing it a pain.

ecstatic-morse (Jan 12 2020 at 12:34, on Zulip):

Also, I was thinking that trait bounds on const fn should only becomeconst if #![feature(const_trait_impl)] is enabled. This would preserve the meaning of const fn foo<T: TraitWithAssocConst>() {} on libraries using #![feature(const_fn)]. I think it's premature to break such code since RFC 2632 hasn't been accepted yet, but maybe you disagree.

ecstatic-morse (Jan 12 2020 at 12:40, on Zulip):

I don't have as much time to work on rustc as I did last year, but I'll keep moving in this direction. I'll try to coordinate with you a bit better in the future.

oli (Jan 13 2020 at 07:45, on Zulip):

We are allowed to break the const_fn feature. In fact I hope we can eliminate it entirely after ?const is implemented

oli (Jan 13 2020 at 07:47, on Zulip):

You can make it a TraitBoundModifier variant if you explicitly make ?const ?Trait a hard error (and ??const Trait as well)

oli (Jan 13 2020 at 07:48, on Zulip):

That's OK to do because opt outs are unstable except for Sized

oli (Jan 13 2020 at 07:48, on Zulip):

And all Sized impls are const

oli (Jan 13 2020 at 07:49, on Zulip):

Wrt ItemKind, seems unfortunate to add another field to this insanely big variant, but i don't see a better way, too

oli (Jan 13 2020 at 08:43, on Zulip):

If you want the breaking change to be less painful for nightly users you can keep allowing it under const_fn and we'll deprecate the feature in a separate PR

oli (Jan 13 2020 at 08:44, on Zulip):

Wrt time to work on it. Take all the time you need, there's no pressure. It's awesome that you are moving this forward!

ecstatic-morse (Jan 22 2020 at 21:38, on Zulip):

I would say the "easy" part is now implemented (parsing and lowering). I was wondering if @oli had any advice for the trait solving aspect. My conception was the following: use the trait solver to solve for the impl or bound that provides each method call during const-checking, then see if that impl is a impl const Trait or if the bound which provides is a const one (i.e. doesn't have the opt-out). As you can probably intuit, I don't have a great idea for what stage we associate method calls with inherent impls/generic bounds, so any pointers to code where similar things are done would be great.

ecstatic-morse (Jan 22 2020 at 21:46, on Zulip):

There's also the task of deprecating the current semantics for unadorned bounds in const fn and directing people to use ?const. I had mentioned that it would be possible to have #![feature(const_fn)] preserve the existing semantics for trait bounds at the cost of slightly increased complexity in the const checker. I would have liked to have removed qualify_min_const_fn entirely by this point; it would have made the back-compat idea easier since #![feature(const_fn)] wouldn't serve double duty. Since that pass still exists, I think we should not try to do the back-compat thing.

oli (Jan 23 2020 at 15:08, on Zulip):

I think as a first step we're going to need a query is_const_impl because otherwise we lose this information cross crate

oli (Jan 23 2020 at 15:09, on Zulip):

As a second step I'd recommend to implement a check that enforces that all functions inside an impl const Trait adhere to const fn

oli (Jan 23 2020 at 15:11, on Zulip):

and only then do the trait solving (which as you correctly inferred is the hardest part here)

oli (Jan 23 2020 at 15:11, on Zulip):

you can still split this step into two

oli (Jan 23 2020 at 15:12, on Zulip):

actually... maybe even into three

oli (Jan 23 2020 at 15:13, on Zulip):

so the first substep could be to permit monomorphic calls to trait methods. So const FOO: u32 = 1u32.add(2u32);

oli (Jan 23 2020 at 15:14, on Zulip):

and here's the point where I also don't know what parts of the compiler you need to touch

oli (Jan 23 2020 at 15:15, on Zulip):

the second substep would be to allow having trait bounds on generics of const fn and calling them inside the const fn

oli (Jan 23 2020 at 15:15, on Zulip):

the fourth substep would be to permit calls to generic const fn by doing the trait solving that you hinted at in your message

oli (Jan 23 2020 at 15:19, on Zulip):

so the first substep could be to permit monomorphic calls to trait methods. So const FOO: u32 = 1u32.add(2u32);

Note: do not change libcore/libstd yet, we first need a rustc_const_unstable scheme for that

ecstatic-morse (Jan 23 2020 at 22:17, on Zulip):

@oli Sounds like a good plan. I'll work on the first two when I have time and try and find someone who's familiar with the trait solver API to point me in the right direction.

ecstatic-morse (Feb 05 2020 at 05:24, on Zulip):

The first two are done in #68847. It's nice and small for now, although the test suite isn't complete and I haven't dealt with rustc_const_unstable. I've also looked into the type param part a bit more. My tentative plan is to make the trait solver/method prober return the "root predicate" where it found a trait method. Still not sure how easy/hard this will be. An alternative is to have the trait solver actually consider the constness of impls/bounds when solving, and have type checking run in the current, const-unaware mode and const-checking ran in the const-aware mode.

Last update: Apr 04 2020 at 23:55UTC