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.
CodePudding user response:
This doesn't seem a job for generics, because you still need specific code for each different type. Generics shine when your generic code runs the same operations on all types constrained by T.
In case of the predeclared types string and int, there isn't a common operation to initialize their value from a string. However...
Is there a way to provide implementations for all possible FieldTypes?
The least verbose solution I can think of is using a type switch on the type T. You take advantage of the fact that Field struct already has a field defaultValue of type T:
func (f *Field[T]) Get() (T, error) {
value, ok := os.LookupEnv(f.name)
if !ok {
return f.defaultValue, nil
}
var ret interface{}
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
}
Notes:
- you must convert
defaultValueto aninterface{}/anyin order to use it in a type switch. You can't type-switch directly on something of typeT. - you still must use
interface{}as a variable to temporarily hold the return value. Assignment toTis allowed only if the given concrete type is assignable to each specific type inT's type set. - you type-assert
ret.(T)when returning (remember thatretwas aninterface{}, notT). Beware that this assertion is unchecked, so it may panic if for some reasonretholds something that isn't in the type set ofT
GoTip playground with map to simulate os.LookupEnv: https://gotipplay.golang.org/p/gK9W3AicfMt
CodePudding user response:
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 ;-)
