Collapsing meta-discussion
I don’t think it’s very feasible to answer the question “is this feature a good idea?” without meaningful discussion about “what could you do with this feature that you can’t do (or would be difficult to do) today?”
I agree that many users probably follow the thought path where they never get to the point of asking “I know I can’t do this, but what if the language were different?” However, once we’re at the point where someone has raised the question of whether we should change the language, I think asking for motivating use cases is a highly relevant and necessary question—the second section of an evolution proposal is Motivation after all.
That’s not to say that every proposal has to revolutionize the type system or present some major new capability. But I think the motivation must at least be non-circular, that is, not just a restatement of the feature itself. To be concrete, the motivation for "should we have struct/enum constraints?" can't just be "it would enable us to prevent non struct/enum types from being passed." That's just another way of describing the feature, not motivation for the feature.
No disrespect taken! I don't mean to suggest it's trivial, nor do I think it's necessarily the responsibility of someone opening a discussion thread to provide such an example. It's fine to say "it occurred to me that I can't do this, I'm wondering if we should make it possible." I myself have opened many such threads!
That said, it should be the community's goal as a whole, to answer the question "would such a feature be useful?" The question is an invitation to creativity, not meant to stifle it. If no one is able to even hypothesize about a situation where a hypothetical feature would be useful, I think it's unlikely to make a compelling addition to the language.
To specifically talk about precise generic constraints for reference types vs. value types, I think we should think about specific capabilities of the constraints you've pointed out. Apple-platform AnyObject conversion notwithstanding (I agree that's a not-ideal pitfall), there are semantically meaningful things that you can do with AnyObject such as check for reference identity with === and hold weak references.
Actor constraints (and specifically refining the Actor protocol with another protocol) allows actors to conform without having to mark the requirement witnesses as nonisolated:
protocol P: Actor {
var x: Int { get }
}
protocol Q {
var y: Int { get }
}
actor A: P {
var x: Int { 0 }
nonisolated var y: Int { 1 } // y has to be nonisolated!
}
func f<T: P>(t: T) async -> Int {
await t.x
}
The GlobalActor protocol provides direct access to the shared actor instance and was proposed alongside the future direction that you could one day make types generic over the global actor that they are synchronized on:
@GA
struct S<GA: GlobalActor> { ... }
Precise class constraints like T: UIView are only meaningful for classes because there are multiple types T which might satisfy the constraint, but since value types don't support inheritance, T: SomeStruct is no different than T == SomeStruct which is the same as just accepting SomeStruct directly.
What would you be doing within BaseAnalyticsEvent that would somehow rely on this exhaustivity? Using switch is always exhaustive—the only difference with enums is that you get exhaustivity checking (i.e., you don't have to use default) by naming a finite set of cases, but that's only possible when you know the concrete enum type you're switching over. What does the code look like that's relying on this 'exhaustivity' property of a hypothetical enum-constrained type? (Also worth noting that non-frozen enums in resilient libraries are non-exhaustive, and there's been discussions about extending this capability to non-resilient libraries as well).
I think the case for a general AnyValue constraint is probably stronger that more fine-grained struct or enum constraints, though it's also worth noting that it would be perfectly valid for types to satisfy both AnyObject and AnyValue constraints (e.g., any immutable class type), and it's also the case that top-level structs and enums aren't always valid AnyValues (e.g. all the pointer types, anything that wraps a class without carefully maintaining value semantics like Array does).