如何分配或返回由联合约束的通用类型T?

huangapple go评论82阅读模式
英文:

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 如何分配或返回由联合约束的通用类型T?

huangapple
  • 本文由 发表于 2022年2月9日 18:25:19
  • 转载请务必保留本文链接:https://go.coder-hub.com/71047848.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定