Honestly, for the eventual RFC I'd like to see a clear, centralized good argument to exactly why just using : const Trait and : ?const Trait is poorer enough that we should instead introduce the new ~const sigil.
I think I understand the reasoning behind the new sigil[1], but the reasoning hasn't been written down in one central referencable location yet.
[1] My understanding:
We very much want : const Trait to require the impl to be const, such that the trait can be used in always const context (such as associated const) in this impl, whether this impl is being used as const or not
and even if it doesn't, we still want this semantic available and marked in the signature as significant, not e.g. implicit based on use
Using ?Sized as our reference point, : ?const Trait means that we the impl may or may not be const, and we have to assume the weakest position (that it isn't)
Due to reasons[2], : Trait, even in const fn/impl const, must mean : ?const Trait
And that leaves us without any syntax space for : ~const Trait, with our desired semantics of "const Trait when used for this impl in a const context, ?const Trait when used for this impl in a nonconst context."
[2] The problem is that we failed to fully prevent the use of generic bounds in const fn, so it's possible to introduce a bound in a const context currently, where it has the semantics of : ?const Trait, and to use it meaningfully. And importantly, I think there's an example of usage of this hole in the wild (and we didn't patch it when it was first discovered, implicitly endorsing it).
The original RFC draft had : Trait in a const context as : ~const Trait. Assuming this is correct in that we can't have this semantic in current editions, I'd like to see an eventual RFC discuss the possibility of making this the default in future editions, warning previous editions on not specifying ?const, and only having ~const as the "desugaring" of the default and for editions where ?const is the (undesirable, but forced) default.
const fn foo<T>()
where
PhantomData<T>: /* ?const */ Trait
{
// compiles on stable rustc 1.31.0 - current 😭
}
This description of why ~const also explains why the apparent asymmetry of impl <Ty: ~const Trait> const Trait for _; we're providing const Trait, which can be requested directly, even if it's more common to request ~const Trait.
Addendum: one additional wrinkle to consider is async fn and any implications const Trait syntax has on people intuiting to have async Trait mean something, and thus ~async Trait bounds in async fn f<T: Trait>()... which could be good or bad depending on where the language is going. In the future where the default is changed to ~const I'd potentially be worried about teaching why const fn makes its type arguments ~const while async fn doesn't have a similar effect[3], I suppose...
We can't use : const Trait to mean "const bound if called from a const context, regular bound if not", because it is inconsistent. And in the future we might give meaning to const modifiers, such as const fn(T) -> T which requires the function it points to to always be const. This could be useful for for example a macro, given the name of a function, ensures that the function is const.
We can't implicitly change the meaning of T: Trait in const fns because it comes in the way of future extensions. For example, function pointers and dyn Traits are alllowed in consts: const C: &dyn Trait and const C: fn() -> i32 are both stable today. If we introduce const modifier to them instead, it becomes inconsistent with T: Trait on const fns meaning what we have as T: ~const Trait today.
I don't see how this specifically is a problem. dyn Trait and fn() don't show up in generic bounds. However, I do see where &dyn ~const Trait and ~const fn() are things that would be desired in the future, whether ~const is implied in generic bounds or not. (Plus, argument impl Trait is fun... would it follow the dyn Trait default of ?const or the generic argument default of ~const in this world?)
If we see it as always "desugaring" to ?const (allows nonconst), ~const (const if used as const), or const (always const), there's (imho) no inherent problem/inconsistency with generic arguments of const items defaulting to requiring ~const bounds as convenience for the common case (especially since you can make an argument that it's a ~const fn definition, anyway, since it may not be const if provided with nonconst arguments and used in a nonconst context).
It's kind of interesting tbh that constalready effectively means const only in a const context. If anything, you can make an argument we want a stronger modifier for generic arguments that are required to be fulfilled with const-capable functionality.
In pure jest, I counterpropose that T: const Trait is only const when the defining item is used as const, and that a new syntax T: const!!! Trait is added for when T needs to always provide a const impl of Trait.
Also in pure jest, how about east const; T const: Trait and/or T: Trait const (but please no don't both allow this and give it subtly different semantics)
I've wanted to have bounds on const methods and have never even considered such a workaround. I understand that it's possible that some have used it; I am interested in how prevalent reliance on the accidental stabilization actually is.
Likely it's too subtle of a distinction, but I'm making a difference between : AsRef<...>, as the generic bound, and dyn Debug, as a generic parameter (which happens to be within the generic bound, but only the immediate context matters).
@CAD97 to be clear, at least according to my understanding this syntax is entirely provisional. The WG needs some syntax to keep experimenting in this space; hopefully this syntax will not have to be stabilized.
For your actual point, I do think it would be very confusing if these meant very different things:
For foo2, we have little choice but make it mean "x points to a type that implements Trait in any way, potentially non-const, so we cannot call trait methods". So I think if the situation in foo1 was different, that would be extremely confusing -- and that extends to foo0.
Is it possible that this could land as part of a future edition? cargo fix --edition could easily convert impl Trait to impl Trait + ?const. The only disadvantage (other than waiting until 2024) is that this would limit the use (though not implementation) of const traits in editions ≤ 2021.
What if trait methods with a default implementation couldn't be called from a const context at all unless they were specifically annotated as const fn?
No, because any existing const impl of that trait would have an existing const-friendly implementation which would be used instead of the default implementation.
Likewise you could add additional new non-const fn methods with a default impl to the trait, they just wouldn't be callable from a const context unless an explicit implementation were added to the const impl.
I'm suggesting that an alternative to "breaking" when adding something that isn't const-friendly is instead that functionality isn't usable in const contexts instead.
In that case the caller isn't expecting to use that functionality in a const context, so why should it break the caller just because some functionality was added which they aren't using?
I know there have been several proposals like this around object safety, namely that adding something which isn't object safe shouldn't break object safety for the whole trait, but rather only the "object safe subset" of the trait is available in that context. I think it even worked that way at one point, but it was changed.
Perhaps something like that could work here, and const callers could use the "const-safe" subset of a trait, allowing default methods which aren't declared "const-safe" to be added without breaking existing const impls.
That would require const-checking for the monomorphized MIR. And have bad performance. For T: ~const SomeTrait we need to go through the function and see its function calls to T, and check if the caller has const implementations for those functions.
Well, library authors should just split that kind of trait into two. One that is possible to have const implementations and one that is not.
In the future we might have some elaborated syntax like where <T as SomeTrait>::some_fn : ~const for fine-grained control for which function should have const implementations. Your suggestion is just inferring these fine-grained bounds from the method body.
After writing impl const From<OtherType> for MyType, I was expecting the corresponding blanket impl to be impl const Into<MyType> for OtherType, but it appears to be merely impl Into<MyType> for OtherType (note the lack of impl const for Into).
I came here to see if this simply hasn't been implemented yet, but saw no mention of this in the RFC. Am I raising this issue in the correct place?
My workaround will be to simply create the impl const Into<MyType> from OtherType directly, as this will provide the best ergonomics for my users, but, of course, there will be no corresponding From impls any more, which will be surprising/unidiomatic.
I gave it a try, but the compiler (rustc 1.57.0-nightly (a8f2463c6 2021-10-09) did not recognize the const_convert feature. Updated and same result for rustc 1.58.0-nightly (bd41e09da 2021-10-18). I also didn't see this feature listed in the Rust Unstable book.