Last active
November 30, 2023 02:41
-
-
Save mumbleskates/f6328a10dcefa4445b6cee7683f8cb1e to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// It is possible to add golang interfaces together. | |
// But did you know that you can also subtract them? | |
// Here are two arbitrary interfaces. They can have any | |
// methods. | |
type A interface { | |
Foo() string // This is the conflicting method signature. | |
Bar() int | |
} | |
type B interface { | |
Foo() string // Both A and B have it. | |
Baz() bool | |
} | |
// This is a normal combined interface: it has the union of | |
// all methods in both A and B. | |
type AunionB interface { | |
A | |
B | |
} | |
// The above doesn't work if there are colliding methods with | |
// the same name but different signatures, you have to spell | |
// the intersection manually with all the appropriate method | |
// names (though the arguments and return types don't matter, | |
// and the resulting struct at the end won't match A's | |
// interface anyway which might defeat the point. But this | |
// can be done with zero bytes of overhead, so that's nice!) | |
// This struct's interface is only the methods that are *not* | |
// common between A and B; any methods that are common are | |
// ambiguous when called, so they are not callable and | |
// therefore missing. | |
type AdiffB struct { | |
A // both of these fields are interface *objects* | |
B | |
} | |
// This struct's interface is the *intersection* of the | |
// methods in A and B: they can't be dispatched to AdiffB | |
// because they conflicting there, so they are always sent to | |
// AunionB. Methods not in the intersection of A and B are | |
// present in both types this struct inherits from and so | |
// are missing. | |
type AintersectB struct { | |
AunionB | |
AdiffB | |
} | |
// This type's interface is only methods in A but not B... | |
type Aonly struct { | |
A | |
AintersectB | |
} | |
// And finally, we have a struct which has an interface | |
// matching AunionB, with a B interface object field that is | |
// dispatched to for all its methods, if possible, and any | |
// methods not part of the B interface are dispatched to the | |
// nested A interface object. | |
type AoverlaidwithB struct { | |
Aonly | |
B | |
} | |
// This type matches either or both interfaces. | |
var _ A = AoverlaidwithB{} | |
var _ B = AoverlaidwithB{} | |
var _ AunionB = AoverlaidwithB{} | |
// Here's a couple concrete types we can put into those | |
// interfaces to demonstrate. | |
type Areal struct{} | |
func (a Areal) Foo() string { | |
return "real A impl" | |
} | |
func (a Areal) Bar() int { | |
return 5 | |
} | |
type Breal struct{} | |
func (b Breal) Foo() string { | |
return "real B impl" | |
} | |
func (b Breal) Baz() bool { | |
return true | |
} | |
func main() { | |
x := AoverlaidwithB{ | |
Aonly: Aonly{A: Areal{}}, | |
B: Breal{}, | |
} | |
// All the other nested fields can be left nil, as they will | |
// never be automatically dispatched to. The total overhead | |
// for all these shenanigans is 3 empty interface objects, | |
// or 48 bytes of zeros on a 64b platform. | |
fmt.Printf( | |
"foo: %q, bar: %d, baz: %t\n", | |
x.Foo(), x.Bar(), x.Baz(), | |
) | |
// prints: | |
// foo: "real B impl", bar: 5, baz: true | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment