Skip to content
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

proposal: spec: interfaces should not be able to contain type sets, define a new thing instead #50836

Closed
virtuald opened this issue Jan 26, 2022 · 5 comments

Comments

@virtuald
Copy link

Disclaimer: I'm putting this out quickly without a ton of detail because I realize the odds of this being accepted are really low this late in the go 1.18 process. If there's any chance this could be accepted, I'm willing to spend a bit more time fleshing it out this weekend. I expect perhaps it's too late to consider this, and I imagine that it was discussed to death in earlier rounds of generics -- but I would argue that some of that discussion happened prior to some of the simplification of type sets, so perhaps it wasn't as compelling?

If the language releases go 1.18 as is, then we are forever stuck with interfaces that can contain type sets and this proposal would no longer make sense as post-1.18 it would introduce multiple ways to do the same thing, which is not very go-like.

Disclaimer 2: I've only been really trying to use generics for 2 days, but I hope my fresh perspective is useful in this case

Proposal

Constraints that contain type sets may only be a "constraint" type instead of an "interface". For example:

type Signed constraint {
	~int | ~int8 | ~int16 | ~int32 | ~int64
}

type IntWithFn constraint {
	~int
	Fn()
}

This would no longer be allowed:

type Signed interface {
	~int | ~int8 | ~int16 | ~int32 | ~int64		// compile error
}

type I interface {
	MyConstraint		// compile error
}
  • The current rules for a interfaces with type sets would apply now to constraint types -- so a constraint could have both type sets and functions in its definition
  • Interfaces could still be used as generic constraints, but only to constrain functions, and not type + function
  • A constraint without a type set should probably be allowed, but can only be used in generic constraints
    • Would have to think more about the ramifications of this

Rationale

When I first started playing with generics, it took me some time before I needed to play with type sets. I created my own for something and since "it was an interface" I started trying to use it like an interface in the same places that you would normally use an interface (for example, #50813). But you can't do that, and it wasn't really clear to me immediately why that was.

From the type parameters proposal: "Go already has a construct that is close to what we need for a constraint: an interface type". But it's close. It's not the same, and shouldn't be the same.

I've definitely struggled at first to understand where I can use these new magic interfaces and where I can't use them. I believe that if they were separate things then it would be easier to bridge that gap.

Interfaces with type sets are not like other interfaces and cannot be used in most other places that an interface can be used

// nope, can't use interface here: interface contains type constraints
func UseInterface(c InterfaceWithTypeSets) {}

type Foo struct {
	// nope, can't use interface here: interface contains type constraints
	I InterfaceWithTypeSets
}

type Generic[C MyConstraint] interface {
	// surprise: interface contains type constraints
	Fn() C
}

Interfaces with type sets introduce ambiguity

It's also really easy to not notice that a particular interface is a constraint:

type C interface {
	CoolThing
	Fn()
}

What is that? Is that an interface that has an embedded interface, or is it an interface that can only be used as a constraint? Without knowing what CoolThing is, it's ambiguous to a human reading the code.

// surprise! compile error "interface contains type constraints"
var _ C

I don't know of many other places in golang where things are quite this ambiguous, but that could be a lack of experience.

Magic remote breakage

Here's another dumb idea I thought of -- which honestly this is a stretch, but possible in undisciplined projects. Let's say a library defines an interface that they only use as a generic constraint

type LibraryInterface interface {
	Fn()
}

A user of the library finds it useful, extends it:

type MyInterface interface {
	LibraryInterface
}

Or even worse, returns it because after all "its just an interface":

type MyInterface interface {
	GetLI() LibraryInterface
}

The next release of the library, the since the author was only using the interface as a generic constraint, decides to add a type set to the interface. Now any user that was trying to use that interface like a normal interface is broken.

Granted, this could still happen too if for example the library author changed the type of a particular name from an interface to a struct -- but it's a bit more egregious and more obviously a Bad Thing. If you're already only using an interface in a generic constraint context, "just" adding a type set is definitely a lot more non-obvious that you're going to break consumers of your library.

Why shouldn't you accept this proposal

  • Having a constraint type also implies that interfaces cannot be used as a constraint for a generic function/etc, which wouldn't be true.
  • Probably a lot of work on the compiler, would almost certainly delay go 1.18 and break any existing code written for the beta, and require a second beta release
  • Adding another thing to learn seems heavy handed (I argue the separation between interface and constraint is worth it!)
  • How often will these kind of interface constraints be used anyways? Is this really all that important?

Other

  • I think this would simplify the question in spec: document definition of comparable #50791 -- comparable becomes a constraint type, instead of a magic interface
  • I have to imagine the compiler already is special casing interfaces that have type sets already, so it might not be that much work to implement this?
  • Probably would make parsing constraints/interfaces simpler? (Confusing error messages when parsing interface that isn't a constraint: cmd/compile: better error message syntax error in interface types #49205)
  • Any future operations on type sets would probably be easier to parse/process since you don't have to grok whether it's an interface or an interface with constraints
  • The "interface contains type constraints" compiler error could be clearer now:
    • constraint cannot be used as a variable
    • constraint cannot be used as a function parameter
    • etc

Finally

Thanks for your consideration.

@ianlancetaylor
Copy link
Contributor

Thanks for the proposal.

This would give us two kinds of things that are very similar: interfaces and constraints. That would bring us back to a path that we were on before, in which we named (what we now call) constraints as contracts. See https://go.googlesource.com/proposal/+/refs/heads/master/design/go2draft-contracts.md . When we presented that approach, many many people were concerned about the similarity between interface types and contracts--and they were actually more different than interfaces and constraints in this proposal. Because of those objections we moved away from contracts and consolidated on interface types.

Also, it's too late. After years of work we've accepted a proposal that uses interface types as constraints, and, while we could tweak that, we aren't going to radically change it at this point.

Therefore, we aren't going to adopt this change, and I'm going to close this issue. Sorry.

@ianlancetaylor
Copy link
Contributor

@wxolp I don't think this issue is a good place to discuss that topic. What you are discussing seems more like #41716, only updated to the current syntax and the notion of type sets.

@ianlancetaylor
Copy link
Contributor

Sum types in general is #19412.

@golang golang locked and limited conversation to collaborators Jun 17, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

4 participants
@virtuald @ianlancetaylor @gopherbot and others