Description
Go Programming Experience
Novice
Other Languages Experience
Python, Java, JS
Related Idea
- Has this idea, or one like it, been proposed before?Does this affect error handling?Is this about generics?Is this change backward compatible? Breaking the Go 1 compatibility guarantee is a large cost and requires a large benefit
Has this idea, or one like it, been proposed before?
No
Does this affect error handling?
No
Is this about generics?
Yes, this proposal is fundamentally about enhancing the Go generics system introduced in Go 1.18. It relates directly to the accepted design by proposing added flexibility within generic functions. Currently, the compiler's understanding of a type parameter T
is fixed by its declared constraint throughout the function body. This proposal suggests allowing the compiler's understanding of T
's capabilities to be conditionally refined within specific code blocks based on checks against stricter constraints.
Proposal
1. What is the proposed change?
We propose introducing a language mechanism that allows developers to check if a type parameter T
satisfies a specific constraint C
(where C
might be stricter than the constraint T
was originally defined with). Crucially, if this check succeeds, within the subsequent code block (e.g., the if
block), the compiler should statically recognize that T
satisfies constraint C
. This enables the direct use of functions, methods, or operations that require constraint C
on values of type T
, without needing further runtime type assertions or switches for dispatch within that block.
Consider the motivating example: writing a function getComparator[T any]()
that returns cmp.Compare[T]
if T
happens to satisfy cmp.Ordered
, and nil
otherwise.
Current Limitation:
import "cmp"
func getComparatorCurrent[T any]() func(a, b T) int {
// Runtime check is possible
var zero T
if _, ok := any(zero).(cmp.Ordered); ok {
// PROBLEM: Inside this block, the compiler *still* doesn't know T satisfies cmp.Ordered.
// return cmp.Compare[T] // This fails to compile: T does not satisfy cmp.Ordered
// Workaround: Return a closure with runtime dispatch
return func(a, b T) int {
switch x := any(a).(type) { // Requires runtime switch
case int: return cmp.Compare(x, any(b).(int))
case string: return cmp.Compare(x, any(b).(string))
// ... other ordered types ...
default: panic("unhandled ordered type")
}
}
}
return nil
}
The current workaround requires runtime checks and often leads to verbose type switches inside closures, losing some of the elegance and static safety benefits of generics.
Proposed Semantics (Syntax TBD):
We need a way to express the conditional check and the resulting type narrowing. Hypothetical syntax examples:
// Hypothetical Syntax 1:
import "cmp"
func getComparatorProposed[T any]() func(a, b T) int {
if check T satisfies cmp.Ordered { // Hypothetical check construct
// Inside this block, the compiler *knows* T satisfies cmp.Ordered.
return cmp.Compare[T] // This should now compile successfully.
}
return nil
}
// Hypothetical Syntax 2 (alternative):
import "cmp"
func getComparatorProposed2[T any]() func(a, b T) int {
if T implements cmp.Ordered { // Hypothetical, reusing 'implements' concept
// Compiler knows T satisfies cmp.Ordered here.
return cmp.Compare[T]
}
return nil
}
The precise syntax is secondary to the core semantic proposal: a mechanism to conditionally narrow the effective constraint of a type parameter within a limited scope, based on a check, thereby informing the compiler's static analysis within that scope.
2. Who does this proposal help, and why?
This proposal helps developers writing generic Go code, particularly those creating:
- Generic Algorithms: Algorithms that can have optimized paths if the input type supports certain operations (e.g., sorting using
cmp.Ordered
vs. requiring a custom comparator). - Generic Data Structures: Structures that might offer additional features or performance benefits if the element type
T
satisfies specific constraints beyond the basic requirements. - Utility Libraries: Functions that need to adapt their behavior based on the capabilities of the type parameter.
Why it helps:
- Cleaner Code: Reduces boilerplate associated with runtime type checks and dispatch (e.g., type switches inside closures). Code becomes more declarative ("if T can do this, do this specific thing").
- Improved Type Safety: Allows leveraging the compiler's static checks within the conditional block, reducing reliance on
any
conversions and runtime assertions which can fail. - Potential Performance Gains: Enables direct calls to constraint-specific generic functions (like
cmp.Compare[T]
) instead of potentially slower runtime dispatch mechanisms. - Enhanced Expressiveness: Makes generics more powerful for handling types with varying capabilities in a structured way.
Language Spec Changes
No response
Informal Change
No response
Is this change backward compatible?
Yes, this proposal is intended to be backward compatible with Go 1. It introduces a new feature or syntax. Existing code that does not use this feature would remain unaffected in compilation and behavior.
Orthogonality: How does this change interact or overlap with existing features?
No response
Would this change make Go easier or harder to learn, and why?
No response
Cost Description
No response
Changes to Go ToolChain
No response
Performance Costs
No response
Prototype
No response
Activity
gabyhelp commentedon Mar 27, 2025
Related Issues
(Emoji vote if this was helpful or unhelpful; more detailed feedback welcome in this discussion.)
thepudds commentedon Mar 27, 2025
Hi @viocha, I haven't digested everything you wrote, but could you compare and contrast this with #45380? Is there substantial overlap?
Separately, gabyhelp posted some possibly related issues. There is also #49206. Do you see overlap with any of those?
viocha commentedon Mar 28, 2025
Yes, it's duplicated with #45380