英文:
Generic function to work on different structs with common members from external package?
问题
我想编写一个单一函数,可以向Firebase消息结构添加特定字段。有两种不同类型的消息,Message
和MulticastMessage
,它们都包含相同类型的Android
和APNS
字段,但是消息类型之间没有明确声明的关系。
我认为我应该能够这样做:
type firebaseMessage interface {
*messaging.Message | *messaging.MulticastMessage
}
func highPriority[T firebaseMessage](message T) T {
message.Android = &messaging.AndroidConfig{...}
....
return message
}
但是它会报错message.Android undefined (type T has no field or method Android)
。而且我也不能写switch m := message.(type)
(cannot use type switch on type parameter value message (variable of type T constrained by firebaseMessage)
)。
我可以写switch m := any(message).(type)
,但我仍然不确定这样做是否会达到我想要的效果。
我在其他一些Stack Overflow的问题中找到了一些关于联合和类型约束的困惑,但是我没有看到任何解答能够解释为什么这样做不起作用(也许是因为我试图将其用于结构体而不是接口?)或者联合类型约束实际上有什么用处。
英文:
I want to write a single function that can add certain fields to Firebase message structs. There are two different types of message, Message
and MulticastMessage
, which both contain Android
and APNS
fields of the same types, but the message types don't have an explicitly declared relationship with each other.
I thought I should be able to do this:
type firebaseMessage interface {
*messaging.Message | *messaging.MulticastMessage
}
func highPriority[T firebaseMessage](message T) T {
message.Android = &messaging.AndroidConfig{...}
....
return message
}
but it gives the error message.Android undefined (type T has no field or method Android)
. And I can't write switch m := message.(type)
either (cannot use type switch on type parameter value message (variable of type T constrained by firebaseMessage)
).
I can write switch m := any(message).(type)
, but I'm still not sure whether that will do what I want.
I've found a few other SO questions from people confused by unions and type constraints, but I couldn't see any answers that helped explain why this doesn't work (perhaps because I'm trying to use it with structs instead of interfaces?) or what union type constraints are actually useful for.
答案1
得分: 6
在Go 1.18中,您无法访问类型参数的公共字段<sup>1</sup>或公共方法<sup>2</sup>。这些功能之所以不起作用,是因为它们在语言中尚不可用。如链接的线程所示,常见的解决方案是将方法指定为接口约束。
然而,类型*messaging.Message
和*messaging.MulticastMessage
没有共同的访问方法,并且它们是在一个您无法控制的库包中声明的。
解决方案1:类型切换
如果联合类型中只有少量类型,这种方法效果很好。
func highPriority[T firebaseMessage](message T) T {
switch m := any(message).(type) {
case *messaging.Message:
setConfig(m.Android)
case *messaging.MulticastMessage:
setConfig(m.Android)
}
return message
}
func setConfig(cfg *messaging.AndroidConfig) {
// just assuming the config is always non-nil
*cfg = &messaging.AndroidConfig{}
}
Playground: https://go.dev/play/p/9iG0eSep6Qo
解决方案2:带有方法的包装器
这可以归结为https://stackoverflow.com/questions/28800672/how-to-add-new-methods-to-an-existing-type-in-go,然后将该方法添加到约束中。如果有许多结构体,这仍然不是理想的解决方案,但代码生成可能会有所帮助:
type wrappedMessage interface {
*MessageWrapper | *MultiCastMessageWrapper
SetConfig(c foo.Config)
}
type MessageWrapper struct {
messaging.Message
}
func (w *MessageWrapper) SetConfig(cfg messaging.Android) {
*w.Android = cfg
}
// same for MulticastMessageWrapper
func highPriority[T wrappedMessage](message T) T {
// now you can call this common method
message.SetConfig(messaging.Android{"some-value"})
return message
}
Playground: https://go.dev/play/p/JUHp9Fu27Yt
解决方案3:反射
如果有许多结构体,使用反射可能更好。在这种情况下,类型参数并不是严格必需的,但有助于提供额外的类型安全性。请注意,为了使此方法工作,结构体和字段必须是可寻址的。
func highPriority[T firebaseMessage](message T) T {
cfg := &messaging.Android{}
reflect.ValueOf(message).Elem().FieldByName("Android").Set(reflect.ValueOf(cfg))
return message
}
Playground: https://go.dev/play/p/3DbIADhiWdO
注:
- https://stackoverflow.com/questions/70358216/how-can-i-define-a-struct-field-in-my-interface-as-a-type-constraint-type-t-has
- https://stackoverflow.com/q/71376627/4108803
英文:
In Go 1.18 you cannot access common fields<sup>1</sup>, nor common methods<sup>2</sup>, of type parameters. Those features don't work simply because they are not yet available in the language. As shown in the linked threads, the common solution is to specify methods to the interface constraint.
However the the types *messaging.Message
and *messaging.MulticastMessage
do not have common accessor methods and are declared in a library package that is outside your control.
Solution 1: type switch
This works fine if you have a small number of types in the union.
func highPriority[T firebaseMessage](message T) T {
switch m := any(message).(type) {
case *messaging.Message:
setConfig(m.Android)
case *messaging.MulticastMessage:
setConfig(m.Android)
}
return message
}
func setConfig(cfg *messaging.AndroidConfig) {
// just assuming the config is always non-nil
*cfg = &messaging.AndroidConfig{}
}
Playground: https://go.dev/play/p/9iG0eSep6Qo
Solution 2: wrapper with method
This boils down to https://stackoverflow.com/questions/28800672/how-to-add-new-methods-to-an-existing-type-in-go and then adding that method to the constraint. It's still less than ideal if you have many structs, but code generation may help:
type wrappedMessage interface {
*MessageWrapper | *MultiCastMessageWrapper
SetConfig(c foo.Config)
}
type MessageWrapper struct {
messaging.Message
}
func (w *MessageWrapper) SetConfig(cfg messaging.Android) {
*w.Android = cfg
}
// same for MulticastMessageWrapper
func highPriority[T wrappedMessage](message T) T {
// now you can call this common method
message.SetConfig(messaging.Android{"some-value"})
return message
}
Playground: https://go.dev/play/p/JUHp9Fu27Yt
Solution 3: reflection
If you have many structs, you're probably better off with reflection. In this case type parameters are not strictly needed but help provide additional type safety. Note that the structs and fields must be addressable for this to work.
func highPriority[T firebaseMessage](message T) T {
cfg := &messaging.Android{}
reflect.ValueOf(message).Elem().FieldByName("Android").Set(reflect.ValueOf(cfg))
return message
}
Playground: https://go.dev/play/p/3DbIADhiWdO
Notes:
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论