I've been writing Go professionally for 8 years — distributed systems, observability tooling, databases. Embedding + interfaces cover 99% of real-world use cases. I have never once hit a wall where inheritance would have been the answer.
Can you provide a single concrete production example where classical inheritance solves a problem that interfaces + composition genuinely cannot? Not a toy Animal/Dog hierarchy, but something from a real codebase.
The scheduler/GC arguments are interesting but very hand-wavy. "Approximately 30%" is not something you can eyeball from reading proc.go.
I'll be honest — I feel the pain. We have a large codebase with deeply nested embedding, and the boilerplate for method delegation is real. Some of our types embed 3–4 structs and we end up writing dozens of pass-through methods just to satisfy interfaces.
But adding classical inheritance to fix this is like curing a headache with a guillotine. The problem is real, the solution isn't.
I'd rather see something like Rust's traits, or honestly just better tooling — go generate + a smart delegation generator could eliminate 90% of this boilerplate without touching the language spec at all.
Next proposal: add semicolons, try/catch, and public static void main to Go. Maybe a pom.xml too while we're at it.
@JustSkiv — the GuideDog extends Dog extends Animal hierarchy in your proposal is actually a textbook example of why inheritance breaks down on real domain models.
Consider what happens when product asks for a RobotGuideDog that isn't biologically a Dog at all. With inheritance you'd need to refactor the entire chain or introduce a parallel hierarchy. With interfaces, you just do this:
type Speaker interface {
Speak() string
}
type GuideDog struct {
Name string
Owner string
}
func (g GuideDog) Speak() string { return "Woof!" }
type RobotGuide struct {
Model string
Owner string
}
func (r RobotGuide) Speak() string { return "Beep." }
// Both satisfy Speaker. No hierarchy needed.
This composes cleanly and doesn't lock you into an ontology you'll regret in six months.
Regarding the itab overhead claim — 12–15% only holds in tight-loop microbenchmarks where the CPU branch predictor can't warm up. In any real application with diverse call sites, inline caching brings interface dispatch within 1–2ns of a direct call. The runtime team has measured this extensively.
I appreciate the effort and the research into the runtime, genuinely. But the conclusion doesn't follow from the evidence.
Honestly? I've been waiting for someone brave enough to write this proposal. Every time I port a service from C# to Go I end up with a Base/Derived pattern that Go just can't express cleanly. Embedding is not the same thing.
The fact that you have a working prototype that passes 97% of stdlib is impressive. That alone deserves serious evaluation, not a knee-jerk "Go doesn't do that".
The compatibility section is the part that sold me. If class and extends are truly contextual and no existing code breaks, then the risk is near zero. The Go 1 promise is preserved.
What's the compilation overhead? Does vtable generation add measurable time to cmd/compile?
I was skeptical when I opened this, but the more I read the more I think the design is actually well-constrained. No multiple inheritance, no constructors, no super — it avoids every trap that made C++ and Java hierarchies painful.
The key insight for me is that class/extends are strictly opt-in. You don't have to use them. Existing code that uses embedding and interfaces continues to work identically. This isn't replacing composition — it's adding a tool for cases where composition genuinely falls short.
I'd like to see how encoding/json handles class types, but that's an implementation detail, not a design blocker.
We maintain a large gRPC service mesh at work (~200 services). Our internal framework has a BaseService struct that every service embeds. Right now we have 14 wrapper methods per service just to satisfy the orchestration interfaces. Fourteen. Per service.
If this lands, we could replace all of that with type UserService extends BaseService and be done. That's not a toy example — that's real production code that would get dramatically simpler.
For those who don't know @JustSkiv — he's done some of the best deep-dive content on Go's runtime internals I've seen anywhere. His articles on the scheduler and GC are incredibly thorough. This isn't some random feature request; the author has genuinely studied the codebase at a level most of us haven't.
I've read through the prototype diff and the changes to cmd/compile are surprisingly small for a feature this significant. Most of the heavy lifting is in the new reflect.Class type, which makes sense.
The explicit decision to NOT support slice covariance is the kind of restraint that tells me this was designed by someone who understands the pitfalls, not just the benefits.
I've spent some time reviewing the prototype and the proposal text. A few observations:
The contextual keyword approach is clever — it sidesteps the backward compatibility concern entirely. I verified that no package in the module index uses class or extends as identifiers in type declaration position.
The vtable implementation in the prototype is straightforward and the compiler changes are well-isolated. The reflect changes need work, but the core design is sound.
I think this deserves serious consideration. @griesemer — you've thought more about the type system implications than anyone. What's your take?
Thanks for the ping, @rsc.
I've been reading through this since it was filed and I have to say — the type system extension is remarkably clean. Single inheritance with no covariance on slices avoids the classic Java ArrayStoreException pitfall. The ParentType.Method(receiver) syntax for calling parent methods is consistent with existing Go semantics and doesn't require a new super keyword.
The fact that this is purely additive and doesn't affect existing structs or interfaces is the key property that makes this viable.
I'm comfortable moving this to Likely Accept. We should schedule a detailed design review for the Go 1.28 cycle.
Abstract
This proposal introduces single classical inheritance to Go via two context-sensitive keywords:
classandextends. The goal is to eliminate pervasive boilerplate associated with struct embedding when modeling type hierarchies.Motivation
Go's current approach to code reuse through struct embedding and interfaces works well for many cases, but breaks down when modeling domains with natural type hierarchies. Consider a typical example:
This works for a single level of embedding. In practice, as hierarchies grow deeper, several problems emerge:
BaseEntitycannot call an overridden method onUser— there is no dynamic dispatch. This forces repeated reimplementation rather than extension.*Usercannot be passed where a*BaseEntityis expected. This leads to additional adapter code or use of interfaces solely to simulate subtype relationships, causing unnecessary heap escapes.Proposal
Introduce
classandextendsas context-sensitive keywords, parsed only at the top level of type declarations.Syntax
Semantics
classmarks a type as inheritable. Regular structs are unaffected and cannot be extended.extendsestablishes a single-inheritance relationship.*Useris assignable to*Entity(true subtyping).[]*Useris NOT assignable to[]*Entity, to preserve type safety.What this is not
This is not an attempt to turn Go into Java. The proposal is deliberately minimal:
superkeyword. Parent methods are called explicitly viaParentType.Method(receiver)syntax, consistent with how promoted methods work today.Compatibility
To strictly adhere to the Go 1 compatibility promise,
classandextendsare implemented as contextual keywords, parsed only in type declaration position. Existing code usingclassorextendsas variable names, field names, or in any other position will continue to compile unchanged.Update: @rsc has independently verified that no package in the public module index uses
classorextendsas identifiers in type declaration position. The mechanism is purely additive.Implementation
A working prototype is available at github.com/JustSkiv/go-inherit, based on
go1.26. Update: after fixes from feedback, it now passes 98.1% of the standard library tests (up from 97.3%). The remaining failures are inreflectandencoding/gob, which require updates to handle the newreflect.Classtype metadata.The implementation touches:
cmd/compile: parsing of contextual keywords, vtable generation for class methodsruntime: type metadata extensions for_typeto support fast subtyping checksreflect: support for class type inspectionEstimated total diff: ~3,200 lines added, ~400 removed. Compilation overhead is negligible — vtable generation adds <0.3% to compile time on the standard library benchmark.
/cc @rsc @griesemer @ianlancetaylor