Объяснение проверки, реализует ли значение интерфейс
Я читал "Effective Go" и другие вопросы и ответы, например: проверка компиляции соответствия golang-интерфейса, но, тем не менее, я не могу правильно понять, как использовать эту технику.
Пожалуйста, посмотрите пример:
type Somether interface {
Method() bool
}
type MyType string
func (mt MyType) Method2() bool {
return true
}
func main() {
val := MyType("hello")
//here I want to get bool if my value implements Somether
_, ok := val.(Somether)
//but val must be interface, hm..what if I want explicit type?
//yes, here is another method:
var _ Iface = (*MyType)(nil)
//but it throws compile error
//it would be great if someone explain the notation above, looks weird
}
Есть ли простые способы (например, без использования отражения) проверить значение, если он реализует интерфейс?
7 ответов
Вам нужно только проверить, реализует ли значение интерфейс, если вы не знаете его тип. Если тип известен, эта проверка автоматически выполняется компилятором.
Если вы действительно хотите проверить в любом случае, вы можете сделать это с помощью второго метода, который вы дали:
var _ Somether = (*MyType)(nil)
что приведет к ошибке во время компиляции:
prog.go:23: cannot use (*MyType)(nil) (type *MyType) as type Somether in assignment:
*MyType does not implement Somether (missing Method method)
[process exited with non-zero status]
Что вы делаете здесь, это присвоение указателя MyType
тип (и nil
значение) в переменную типа Somether
, но так как имя переменной _
это игнорируется.
Если MyType
реализованы Somether
было бы компилировать и ничего не делать
Следующее будет работать:
val:=MyType("hello")
var i interface{}=val
v, ok:=i.(Somether)
Также можно использовать Implements(u Type) bool
метод reflect.Type
следующим образом:
package main
import (
"reflect"
)
type Somether interface {
Method() bool
}
type MyType string
func (mt MyType) Method() bool {
return true
}
func main() {
inter := reflect.TypeOf((*Somether)(nil)).Elem()
if reflect.TypeOf(MyType("")).Implements(inter) {
print("implements")
} else {
print("doesn't")
}
}
Вы можете прочитать об этом больше в документации.
Вы также можете взять вышеуказанное решение Alpha и еще больше уменьшить его:
val:=MyType("hello")
var i interface{}=val
v, ok:=i.(Somether)
можно превратить в:
val := MyType("hello")
v, ok := interface{}(val).(Somether)
Если вы пытаетесь протестировать одноразовые методы, вы даже можете сделать что-то вроде:
val := MyType("hello")
v, ok := interface{}(val).(interface {
Method() bool
})
ПРИМЕЧАНИЕ. Убедитесь, что вы очень осторожны с реализациями "приемник указателя" и "приемник значения". Если реализация использует указатель, утверждение не будет выполнено при передаче объекта значения. Если реализация использует приемник значения, оба утверждения пройдут.
// Implement Somether as a POINTER receiver method
func (mt *MyType) Method() bool {
return true
}
func main() {
val := MyType("hello")
v, ok := interface{}(val).(Somether)
fmt.Println(v, ok)
// Output: <nil> false
// Notice the pass by reference
v, ok := interface{}(&val).(Somether)
fmt.Println(v, ok)
// Output: 0xc000010200 true
}
против
// Implement Somether as a VALUE receiver method
func (mt MyType) Method() bool {
return true
}
func main() {
val := MyType("hello")
v, ok := interface{}(val).(Somether)
fmt.Println(v, ok)
// Output: hello true
// Notice the pass by reference
v, ok := interface{}(&val).(Somether)
fmt.Println(v, ok)
// Output: 0xc00008e1e0 true
}
У меня есть решение, которое я использую в дополнение к шаблону обработчика паники.
Я не тестировал исчерпывающе код, но случайное тестирование положительно.
Я приветствую любые предложения по улучшению или другие полезные материалы.
// -------------------------------------------------------------- //
// hasErrIface -
// ---------------------------------------------------------------//
func hasErrIface(v reflect.Value) (error, bool) {
// CanInterface reports whether Interface can be used without panicking
if !v.CanInterface() {
return nil, false
}
// Interface panics if the Value was obtained by accessing unexported struct fields
err, ok := v.Interface().(error)
return err, ok
}
// -------------------------------------------------------------- //
// HasErrKind
// ---------------------------------------------------------------//
func HasErrKind(r interface{}) (err error, isErr bool) {
err = nil
isErr = false
v := reflect.ValueOf(r)
switch v.Kind() {
case reflect.Struct:
errtype := reflect.TypeOf((*error)(nil)).Elem()
if v.Type().Implements(errtype) {
err, isErr = v.Interface().(error)
}
case reflect.Ptr:
err, isErr = hasErrIface(v)
case reflect.Interface:
err, isErr = hasErrIface(v)
}
return
}
// -------------------------------------------------------------- //
// EvalErrKind
// ---------------------------------------------------------------//
func EvalErrKind(r interface{}) (errval error) {
err, isErr := HasErrKind(r)
if !isErr {
errtxt := "Unknown system error - %v :\n%v"
v := reflect.ValueOf(r)
return fmt.Errorf(errtxt, v.Type(), v)
}
return err
}
// -------------------------------------------------------------- //
// PanicHandler
// ---------------------------------------------------------------//
func PanicHandler(errHandler func(error)) func() {
return func() {
var err error
if r := recover(); r != nil {
switch r.(type) {
case ApiError:
err = r.(ApiError)
default:
err = EvalErrKind(r)
}
if errHandler != nil {
errHandler(err)
}
}
}
}
Он работает только с Go 1.18 или выше.
//implements interface
func implInter[T any](obj any) bool {
⠀ _, ok := obj.(T)
return ok
}
var obj any = "some text"
if impleInter[string](obj) {
⠀ newObj, _ := obj.(string)
}
Вы также можете использовать это:
ЕслиMyType
реализуетSomether
интерфейс, это должно скомпилироваться
// use new to create a pointer to type MyType
var _ Somether = new(MyType)
var _ Somether = MyType("")