英文:
How to assign or return generic T that is constrained by union?
问题
换句话说,我如何为联合类型集中的不同类型实现特定类型的解决方案?
给定以下代码...
type FieldType interface {
string | int
}
type Field[T FieldType] struct {
name string
defaultValue T
}
func NewField[T FieldType](name string, defaultValue T) *Field[T] {
return &Field[T]{
name: name,
defaultValue: defaultValue,
}
}
func (f *Field[T]) Name() string {
return f.name
}
func (f *Field[T]) Get() (T, error) {
value, ok := os.LookupEnv(f.name)
if !ok {
return f.defaultValue, nil
}
return value, nil
}
编译器显示错误:
field.go:37:9: 无法将值(类型为string的变量)作为返回语句中的类型T使用
有没有办法为所有可能的FieldType提供实现?
像这样...
func (f *Field[string]) Get() (string, error) {
value, ok := os.LookupEnv(f.name)
if !ok {
return f.defaultValue, nil
}
return value, nil
}
func (f *Field[int]) Get() (int, error) {
raw, ok := os.LookupEnv(f.name)
if !ok {
return f.defaultValue, nil
}
value, err := strconv.ParseInt(raw, 10, 64)
if err != nil {
return *new(T), err
}
return int(value), nil
}
任何提示都将受到欢迎。
英文:
In other words, how do I implement type-specific solutions for different types in a union type set?
Given the following code...
type FieldType interface {
string | int
}
type Field[T FieldType] struct {
name string
defaultValue T
}
func NewField[T FieldType](name string, defaultValue T) *Field[T] {
return &Field[T]{
name: name,
defaultValue: defaultValue,
}
}
func (f *Field[T]) Name() string {
return f.name
}
func (f *Field[T]) Get() (T, error) {
value, ok := os.LookupEnv(f.name)
if !ok {
return f.defaultValue, nil
}
return value, nil
}
the compiler shows the error:
field.go:37:9: cannot use value (variable of type string) as type T in return statement
Is there a way to provide implementations for all possible FieldTypes?
Like...
func (f *Field[string]) Get() (string, error) {
value, ok := os.LookupEnv(f.name)
if !ok {
return f.defaultValue, nil
}
return value, nil
}
func (f *Field[int]) Get() (int, error) {
raw, ok := os.LookupEnv(f.name)
if !ok {
return f.defaultValue, nil
}
value, err := strconv.ParseInt(raw, 10, 64)
if err != nil {
return *new(T), err
}
return int(value), nil
}
Any hint would be welcome.
答案1
得分: 23
错误发生的原因是涉及类型参数的操作(包括赋值和返回)必须对其类型集中的所有类型都有效。在string | int的情况下,没有一种常见的操作可以从字符串初始化它们的值。
然而,你仍然有几个选项:
在T上进行类型切换
你可以在具有泛型类型T的字段上进行类型切换,并临时将具体类型的值设置为interface{}/any。然后,在返回之前,将接口断言回T类型。请注意,这种断言是未经检查的,因此如果由于某种原因ret保存了不在T类型集中的内容,它可能会引发恐慌。当然,你可以使用逗号-ok来检查它,但它仍然是一个运行时断言:
func (f *Field[T]) Get() (T, error) {
value, ok := os.LookupEnv(f.name)
if !ok {
return f.defaultValue, nil
}
var ret any
switch any(f.defaultValue).(type) {
case string:
ret = value
case int:
// 实际上不要忽略错误
i, _ := strconv.ParseInt(value, 10, 64)
ret = int(i)
}
return ret.(T), nil
}
在*T上进行类型切换
你可以进一步简化上面的代码,并摆脱空接口。在这种情况下,你获取T类型变量的地址,并在指针类型上进行切换。这在编译时完全经过类型检查:
func (f *Field[T]) Get() (T, error) {
value, ok := env[f.name]
if !ok {
return f.defaultValue, nil
}
var ret T
switch p := any(&ret).(type) {
case *string:
*p = value
case *int:
i, _ := strconv.ParseInt(value, 10, 64)
*p = int(i)
}
// 如果没有匹配的情况,ret将具有零值
return ret, nil
}
请注意,在这两种情况下,你必须将T值转换为interface{}/any,以便在类型切换中使用它。你不能直接在T上进行类型切换。
带有模拟os.LookupEnv的映射的 Playground:https://go.dev/play/p/JVBEZwCXRMW
英文:
The error occurs because operations that involve a type parameter (including assignments and returns) must be valid for all types in its type set.
In case of string | int, there isn't a common operation to initialize their value from a string.
However you still have a couple options:
Type-switch on T
You use the field with the generic type T in a type-switch, and temporarily set the values with concrete types into an interface{}/any. Then type-assert the interface back to T in order to return it. Beware that this assertion is unchecked, so it may panic if for some reason ret holds something that isn't in the type set of T. Of course you can check it with comma-ok but it's still a run-time assertion:
func (f *Field[T]) Get() (T, error) {
value, ok := os.LookupEnv(f.name)
if !ok {
return f.defaultValue, nil
}
var ret any
switch any(f.defaultValue).(type) {
case string:
ret = value
case int:
// don't actually ignore errors
i, _ := strconv.ParseInt(value, 10, 64)
ret = int(i)
}
return ret.(T), nil
}
Type-switch on *T
You can further simplify the code above and get rid of the empty interface. In this case you take the address of the T-type variable and switch on the pointer types. This is fully type-checked at compile time:
func (f *Field[T]) Get() (T, error) {
value, ok := env[f.name]
if !ok {
return f.defaultValue, nil
}
var ret T
switch p := any(&ret).(type) {
case *string:
*p = value
case *int:
i, _ := strconv.ParseInt(value, 10, 64)
*p = int(i)
}
// ret has the zero value if no case matches
return ret, nil
}
Note that in both cases you must convert the T value to an interface{}/any in order to use it in a type switch. You can't type-switch directly on T.
Playground with map to simulate os.LookupEnv: https://go.dev/play/p/JVBEZwCXRMW
答案2
得分: 2
好的,以下是翻译好的内容:
好的,如果使用反射,类型切换就可以工作。
func (f *Field[T]) Get() (T, error) {
raw, ok := os.LookupEnv(f.name)
if !ok {
return f.defaultValue, nil
}
v := reflect.ValueOf(new(T))
switch v.Type().Elem().Kind() {
case reflect.String:
v.Elem().Set(reflect.ValueOf(raw))
case reflect.Int:
value, err := strconv.ParseInt(raw, 10, 64)
if err != nil {
return f.defaultValue, err
}
v.Elem().Set(reflect.ValueOf(int(value)))
}
return v.Elem().Interface().(T), nil
}
但是更好的解决方案是非常受欢迎的;-)
英文:
Ok, the type switch works if reflections are used.
func (f *Field[T]) Get() (T, error) {
raw, ok := os.LookupEnv(f.name)
if !ok {
return f.defaultValue, nil
}
v := reflect.ValueOf(new(T))
switch v.Type().Elem().Kind() {
case reflect.String:
v.Elem().Set(reflect.ValueOf(raw))
case reflect.Int:
value, err := strconv.ParseInt(raw, 10, 64)
if err != nil {
return f.defaultValue, err
}
v.Elem().Set(reflect.ValueOf(int(value)))
}
return v.Elem().Interface().(T), nil
}
But better solutions are very welcome ![]()
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论