Go Generics: Any type in switch

159 views
Skip to first unread message

Matthias Jeschke

unread,
Nov 23, 2025, 12:45:21 PM (12 days ago) Nov 23
to golang-nuts
Hi, 

I'm experimenting with bubbletea for creating a TUI and I came across the following problem:

I have a generic struct declared:

type ResourceUpdateMsg[T any] struct {
value T
}

This is used to create messages, e.g.:

func updateCpuStats() tea.Msg {
cpuStats, _ := cpu.Percent(0, false)
return ResourceUpdateMsg[Cpu]{
Cpu{
Usage: cpuStats[0],
},
}
}

Now, in one update function I'm not interested in the specific type of a message; each message should be routed to another, more specific update function:

switch msg := msg.(type) {

case resources.ResourceUpdateMsg[any]:
resourceModel, cmd := m.resources.Update(msg)
m.resources = resourceModel.(resources.Model)
return m, cmd
         ....
         }

In the other, specific update function the switch distinguishes between different types:

switch msg := msg.(type) {
case ResourceUpdateMsg[Cpu]:
m.Cpu = msg.value
cmd := m.Progress.CpuProgress.SetPercent(m.Cpu.Usage / 100)
return m, tea.Batch(tickCmd(updateCpuStats), cmd)
case ResourceUpdateMsg[Mem]:
m.Mem = msg.value
cmd := m.Progress.MemProgress.SetPercent(m.Mem.Usage / 100)
return m, tea.Batch(tickCmd(updateMemoryStats), cmd)
        ...
        }

But this does not work, the case is never selected in the first update function. I have to write all cases, but each case basically does the same: route the message to the other function.

I'm fairly new to Go and my background is Java / Kotlin. In those languages I would work with inheritance.

Is this possible in Go? To have a "wildcard" type parameter? If not, what would be an alternative to the above?

Will Faught

unread,
Nov 23, 2025, 3:50:45 PM (12 days ago) Nov 23
to Matthias Jeschke, golang-nuts
> Is this possible in Go? To have a "wildcard" type parameter? If not, what would be an alternative to the above?

I don’t believe so. That would require pattern matching on constructors, which Go doesn’t have.

It seems to me you can either assign msg to a non-generic interface and then type-switch over non-generic type possibilities, or invoke methods on msg that implement the type switch cases.

In my opinion, parametrically-polymorphic (generic) code is supposed to behave the same regardless of the instantiated type, so you’re probably heading off into the weeds with this approach. If you want code to behave differently based on the concrete type (ad-hoc polymorphism), that’s what interfaces are for. Don’t mix the two in the same place.

Will

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
To view this discussion visit https://groups.google.com/d/msgid/golang-nuts/11bbc330-ef43-427e-97c4-829052fbc9dfn%40googlegroups.com.

Matthias Jeschke

unread,
Nov 23, 2025, 4:10:23 PM (12 days ago) Nov 23
to golang-nuts
Hi,

Thanks for the response. Seems I have to rethink some habits with Golang.

I'm now using an approach that uses an interface:

type MessageHandler interface {
CanHandle(msg tea.Msg) bool
}

The more general update function now asks whether a sub-model supports a certain message:

type model struct {
systemStats systemstat.Model
}

if m.systemStats.CanHandle(msg) {
resourceModel, cmd := m.resources.Update(msg)
m.resources = resourceModel.(systemstat.Model)
return m, cmd
} else {

A specific model then is responsible for deciding whether a message is relevant for it:

func (m Model) CanHandle(msg tea.Msg) bool {
_, isUsageUpdateMsg := msg.(UsageUpdateMsg)
_, isFanUpdateMsg := msg.(FanUpdateMsg)
_, isGpuUpdateMsg := msg.(GpuUpdateMsg)
_, isFrameMsg := msg.(progress.FrameMsg)
return isUsageUpdateMsg || isFanUpdateMsg || isGpuUpdateMsg || isFrameMsg
}

Feels better than the previous solutions.

Kevin Chowski

unread,
Nov 23, 2025, 6:05:20 PM (12 days ago) Nov 23
to golang-nuts
I think you should be able to declare a common interface that all instantiations of ResourceUpdateMsg implement, and then you can type-assert on that interface and pass it around when any ResourceUpdateMsg is desired.

Runnable example here: https://play.golang.com/p/ciQkOqJSgyw

The main idea is to create an AnyResourceUpdateMsg type, which is an interface that is only satisfied by instantiations of ResourceUpdateMsg (e.g. add an unexported method), and to match against that.
Reply all
Reply to author
Forward
0 new messages