Skip to content

proposal: spec: enable conditional type narrowing based on constraint satisfaction for type parameters #73067

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
2 of 4 tasks
viocha opened this issue Mar 27, 2025 · 3 comments
Labels
LanguageChange Suggested changes to the Go language LanguageChangeReview Discussed by language change review committee LanguageProposal Issues describing a requested change to the Go language specification. Proposal
Milestone

Comments

@viocha
Copy link

viocha commented Mar 27, 2025

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

@viocha viocha added LanguageChange Suggested changes to the Go language LanguageChangeReview Discussed by language change review committee Proposal labels Mar 27, 2025
@gopherbot gopherbot added this to the Proposal milestone Mar 27, 2025
@gabyhelp gabyhelp added the LanguageProposal Issues describing a requested change to the Go language specification. label Mar 27, 2025
@thepudds
Copy link
Contributor

thepudds commented 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
Copy link
Author

viocha commented Mar 28, 2025

Yes, it's duplicated with #45380

@viocha viocha closed this as completed Mar 28, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
LanguageChange Suggested changes to the Go language LanguageChangeReview Discussed by language change review committee LanguageProposal Issues describing a requested change to the Go language specification. Proposal
Projects
None yet
Development

No branches or pull requests

4 participants