Объяснение проверки, реализует ли значение интерфейс

Я читал "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("")
Другие вопросы по тегам