rfcs icon indicating copy to clipboard operation
rfcs copied to clipboard

RFC: `const` functions in traits

Open tgross35 opened this issue 2 years ago • 18 comments

Rendered

This RFC allows marking methods in traits as const.

trait Foo {
    const fn bar(a: i32) -> i32;
}

impl Foo for MyStruct {
    // Implementation must provide a const function
    const fn bar(a: i32) -> i32 {
        a + 100
    }
}

// `<T as Foo>::bar` can then be used in const contexts
const fn use_foo<T: Foo>(a: i32) -> i32 {
    T::bar(a) * 50
}

tgross35 avatar Sep 18 '23 03:09 tgross35

@rustbot label +T-lang

tgross35 avatar Sep 18 '23 03:09 tgross35

Error: Label T-lang can only be set by Rust team members

Please file an issue on GitHub at triagebot if there's a problem with this bot, or reach out on #t-infra on Zulip.

rustbot avatar Sep 18 '23 03:09 rustbot

cc @rust-lang/wg-const-eval

tgross35 avatar Sep 18 '23 04:09 tgross35

EDIT: In hindsight, I realise that I made this while tired and not fully understanding what this RFC is suggesting.

Going to effectively retract my comments here, but will leave it for posterity.


Going to be rather blunt: have you been following the development of const-fn-in-trait at all?

Because, this is a planned goal, but one of the reasons why keyword generics exist in the first place is to solve this problem. This RFC strikes me as being written by someone who hasn't followed that development, and just wants const trait support sooner. The problem is that we really can't offer it much sooner than finishing up the existing proposals.

Sure, we could tag some functions as const in traits, require that said functions are always const, then let you use those in const functions since the "constness" of the methods is never in question. But that's an incredibly restrictive constraint, and it doesn't really gain us much beyond our current system. A lot of the actual functionality folks are reaching for can be achieved with the current const fn system via hacks, and the stuff that can't won't really be helped by this change. All it seems like is a stopgap solution that will not really help much, and just delay the existing const trait effort further.

clarfonthey avatar Sep 18 '23 06:09 clarfonthey

Going to be rather blunt: have you been following the development of const-fn-in-trait at all?

I am not familiar with anything specifically called const-fn-in-trait, nor can I find any specific information on it. If this isn't the same as keyword-generics-initiative/effects/const_generic_const_fn_bounds, do you have a link? Otherwise, I believe I am fairly up to date with what is publicly available.

Sure, we could tag some functions as const in traits, require that said functions are always const, then let you use those in const functions since the "constness" of the methods is never in question. But that's an incredibly restrictive constraint, and it doesn't really gain us much beyond our current system. A lot of the actual functionality folks are reaching for can be achieved with the current const fn system via hacks, and the stuff that can't won't really be helped by this change. All it seems like is a stopgap solution that will not really help much, and just delay the existing const trait effort further.

It is true that this RFC is encompassed by the effects goals. But effects are still very much in the experimentation stage and have a much larger and more complex scope (I have to assume we are still many months away from RFCs), so it did not seem there would be any harm in proposing a minor and fairly unobjectionable subset at this time. I also did some due diligence to ensure this wouldn't conflict 1 2.

I also have to disagree that this isn't useful on its own, I come across uses quite frequently in projects that use a lot of statics (e.g. modular C+Rust projects).

tgross35 avatar Sep 18 '23 08:09 tgross35

I also have to disagree that this isn't useful on its own, I come across uses quite frequently in projects that use a lot of statics (e.g. modular C+Rust projects).

I know I definitely will use this, currently I have to make-do with a bunch of consts in traits and other annoying workarounds. I even considered building an interpreter to take a const from a trait and treat it as code that can be run in CTFE.

My motivating use-case is compile-time checking of properties of a domain-specific language embedded in Rust.

programmerjake avatar Sep 18 '23 08:09 programmerjake

While some form of optional const-ness is needed in traits, regardless of how that is done, always const functions are something that you should be able to do. Since a trait is a contract and so its natural to be able to add const-ness to that contract, and this is exactly what has been done for async, you can add async to the contract to force a function to always be async so extending that to const is only natural.

Plus I would definitely use this if it existed.

Matthew-Chidlow avatar Sep 21 '23 02:09 Matthew-Chidlow

Would refinining a trait method by adding const be a further possibility for this RFC?

bluebear94 avatar Sep 21 '23 07:09 bluebear94

Would refinining a trait method by adding const be a further possibility for this RFC?

I don't intend to cover allowing const implementations where the trait does not require const, just to keep this RFC scoped to the most basic need. However, that is actually called out in the refine RFC as a possibility and I think it would interact well with this.

Thanks for bringing it up, I added that under future possibilities.

tgross35 avatar Sep 21 '23 08:09 tgross35

I'd like to share a concrete usecase for this feature.

In the RFC: Introduce the Store API for great good the Store trait has been artificially split into StoreDangling which exposes a single dangling method and Store itself which exposes the rest of the methods just so that dangling handles can be produced in const contexts -- and thus Vec::new and al. can be const -- without requiring that the entire Store implementation be const (hint: most won't be).

If this RFC were to be accepted, the Store API could be simplified to a single Store trait, with a required const fn dangling(...) method, and everyone would rejoice.

matthieu-m avatar Sep 21 '23 17:09 matthieu-m

Just a small question: how would this work with trait objects? Will const functions still be turned into runtime functions when included in a VTable?

For instance:

trait Foo {
    const fn foo();
}

fn run_any_foo(trait_object: &dyn Foo) {
    trait_object.foo();
}

const fn run_any_foo(trait_object: &dyn Foo) {
    trait_object.foo();
}

If a const trait method is called from a const function, will it all be evaluated at compile time or raise an error? Will a const trait methods have to be included in the VTable, even if it is not necessarily called at runtime?

I don't have any specific ideas for changes to the RFC, I'm just making sure trait objects aren't forgotten. :)

BD103 avatar Sep 22 '23 14:09 BD103

Just a small question: how would this work with trait objects? Will const functions still be turned into runtime functions when included in a VTable?

[...]

If a const trait method is called from a const function, will it all be evaluated at compile time or raise an error? Will a const trait methods have to be included in the VTable, even if it is not necessarily called at runtime?

I don't have any specific ideas for changes to the RFC, I'm just making sure trait objects aren't forgotten. :)

That is a great question!

At runtime, const functions act identical to normal functions so I think it should be the same in traits - that is, the functions will be in the VTable. I am not sure what is best with respect to calling const-fn-in-trait functions from a &dyn Foo - it seems doable at first glance but because that may come with hidden trickiness.

I think maybe it is best to leave that under unresolved questions or future possibilities for now, since more information will probably become apparent at implementation time. I'll update the RFC soon

tgross35 avatar Sep 22 '23 20:09 tgross35

I think maybe it is best to leave thta under unresolved questions or future possibilities for now, since more information will probably become apparent at implementation time. I'll update the RFC soon

I think that can be left for when we get function pointers with const in the type, which is presumably what the methods will essentially be:

pub const fn call_it(f: const fn() -> i32) -> i32 {
    f()
}

programmerjake avatar Sep 22 '23 20:09 programmerjake

Responding after re-reading the RFC, understanding for real what's actually going on. I've marked my previous comment to clarify that I really wasn't responding properly to this RFC, although I don't like deleting things since it makes discussions confusing to read.

I agree that having const fns in traits which are always const, regardless of implementer, is reasonable. The main issue is that semantically, it's weird to distinguish between a trait method which can opt into always being meaningful in a const context, versus one which must be const.

The reason why I had such a knee-jerk reaction to the RFC is because, well… there's the issue of potentially abusing this feature until keyword generics are released, where people start offering const and non-const versions of traits. This will just make migrating back into keyword generics a headache, although I agree that simply not having functionality in the meantime is a bad solution.

Overall it's just really confusing to reason about const because it's effectively backwards what it should be. Since something applicable at const time is also applicable at runtime, we should instead have a nonconst keyword that indicates when something it not available at const time, which would make the logic around other keywords (like mut and unsafe) more consistent with const. Of course, that's not what we have now, and we're stuck with it.

clarfonthey avatar Sep 26 '23 00:09 clarfonthey

I would prefer to postpone this RFC until we have a good implementation of const traits. Landing this RFC would suggest that we'd also work on it, and I don't think we can do that until const the const traits work settles down, as it's already hard enough to keep one major const feature implementation in mind.

oli-obk avatar Oct 06 '23 12:10 oli-obk

Thanks for taking a look! With the potential conflicting work in mind, I don't see this going anywhere anytime soon. I'll leave it around for now though as a draft just to continue collecting any feedback

tgross35 avatar Oct 06 '23 17:10 tgross35

This RFC cannot help with eventually making Iterator const, since adding const to a trait function would be a breaking change.

Indeed, it cannot, but I do not see this as a problem.

We can today have inherent impl blocks which mix-and-match const, async, and neither-const-nor-async associated functions, and arguably we need the same for traits.

I already mentioned the usecase of the Store API. Today, in order to produce a dangling handle in const contexts, which is necessary for a const Vec::new, a separate trait had to be introduced for the one function that requires to be const. It's workable, but not ergonomic.

The clearly better API is for the Store trait to be implemented as both const and non-const -- depending on what's possible -- and for its dangling method to always be const.

In the end, we need the flexibility of both const functions in traits and const traits, neither one subsumes the other, they are just appropriate in different circumstances.

I would prefer to postpone this RFC until we have a good implementation of const traits. Landing this RFC would suggest that we'd also work on it, and I don't think we can do that until const the const traits work settles down, as it's already hard enough to keep one major const feature implementation in mind.

I'm ambivalent on this.

I understand the motivation, however it may also be worth accepting such an RFC to signal that const traits will not be the end of the story, and developers for which const methods in traits would be a better fit may want to wait for it, rather than stabilize work-arounds based on const traits.

matthieu-m avatar Oct 07 '23 10:10 matthieu-m

I understand the motivation, however it may also be worth accepting such an RFC to signal that const traits will not be the end of the story,

We don't even have an accepted const trait RFC 😅

oli-obk avatar Oct 07 '23 11:10 oli-obk

If forcing certain functions of a trait to be const is too restrictive, perhaps adding some generic constraint on what must be const in the trait can solve it ?

const fn get_const_default<T>() 
   where T : Default, <T as Default>::default : Const 
{
    T::default()
}
// `Const` can be a marker trait generated by the compiler I guess

Thomas-Mewily avatar Nov 26 '24 13:11 Thomas-Mewily

If forcing certain functions of a trait to be const is too restrictive, perhaps adding some generic constraint on what must be const in the trait can solve it ?

You rediscovered the WIP effects/keyword generics system :) (of which this was intended to be a compatible subset)

tgross35 avatar Nov 26 '24 17:11 tgross35

If forcing certain functions of a trait to be const is too restrictive, perhaps adding some generic constraint on what must be const in the trait can solve it ?

You rediscovered the WIP effects/keyword generics system :) (of which this was intended to be a compatible subset)

Yep indeed it look like ^^ Thank for the link :) The ?const keyword seem more logical too

Thomas-Mewily avatar Nov 26 '24 23:11 Thomas-Mewily