Приведение к интерфейсу для реализации с приемником указателя

Я хочу проверить, реализует ли полученный элемент типа anyencoding.TextUnmarshaler, чтобы я мог вызвать UnmarshalText для него. Проблема в том, что его метод должен быть приемником указателя, и это, похоже, является источником некоторых проблем.

import (
    "encoding"
    "github.com/stretchr/testify/assert"
    "reflect"
    "testing"
)

type MyStruct struct{ value string }

var _ encoding.TextMarshaler = (*MyStruct)(nil)

func (id MyStruct) MarshalText() (text []byte, err error) {
    return []byte(id.value), nil
}

var _ encoding.TextUnmarshaler = (*MyStruct)(nil)

func (id *MyStruct) UnmarshalText(data []byte) error {
    *id = MyStruct{value: string(data)}
    return nil
}

func TestIfICouldInvokeUnmarshalText(t *testing.T) {
    var ms any = MyStruct{value: "hello"}
    _, ok := (ms).(encoding.TextMarshaler)
    assert.True(t, ok)
    _, ok = (ms).(encoding.TextUnmarshaler)
    //assert.True(t, ok) // this is the first attempt, which will fail
    v := reflect.ValueOf(ms)
    if assert.True(t, v.Type().NumMethod() > 0 && v.CanInterface()) {
        if v.CanAddr() {
            _, ok := v.Addr().Interface().(encoding.TextUnmarshaler)
            assert.True(t, ok)
        } else {
            t.FailNow() // this is where I end up
        }
    }
}

Есть ли какой-нибудь (идиоматический) способ добиться этого?

Вероятно, вы захотите проверить, что переданное значение A) реализует некоторый метод и B) является указателем. А) делается так же, как и утверждение типа, и Б) делается просто так: с помощью отражения проверяется, что это указатель. Нет необходимости проверять, что это указатель и реализует что-то, поскольку это уже проверено A.

Volker 26.06.2024 10:29
Создание API ввода вопросов на разных языках программирования (Python, PHP, Go и Node.js)
Создание API ввода вопросов на разных языках программирования (Python, PHP, Go и Node.js)
API ввода вопросов - это полезный инструмент для интеграции моделей машинного обучения, таких как ChatGPT, в приложения, требующие обработки...
0
1
98
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Вы хотите проверить, реализуется ли значение encoding.TextUnmarshaler и является ли оно указателем. Ты можешь написать:

func IsUnmarshalerPointer(ms any) bool {
    _, ok := ms.(encoding.TextUnmarshaler)
    return ok && reflect.ValueOf(ms).Kind() == reflect.Pointer
}
Ответ принят как подходящий

Во-первых,

var _ encoding.TextUnmarshaler = (*MyStruct)(nil)

гарантирует, что *MyStruct реализует encoding.TextUnmarshaler, так зачем вам это проверять?

Во-вторых, MyStruct не реализует encoding.TextUnmarshaler, и в расширении:

var ms any = MyStruct{value: "hello"}

нет, так что там все правильно.

Причина, по которой MyStruct не реализуется encoding.TextUnmarshaler, заключается в том, что любой его метод не может изменить исходную структуру, поскольку он работает с копией, поэтому любые изменения будут потеряны.

У вас уже есть копия в вашем интерфейсе, поэтому ссылка на исходную структуру все равно теряется (Go Playground):

package main

import (
    "fmt"
)

type MyValue struct{ Value string }

func (m MyValue) SetValue(value string) {
    m.Value = value
}

func main() {
    m := MyValue{"hello"}
    a := any(m)

    m.SetValue("world")
    a.(MyValue).SetValue("goodbye")
    fmt.Println(m, a)

    m.Value = "world"
    fmt.Println(m, a)
}

принты

{hello} {hello}
{world} {hello}

у вас уже другие ценности. В качестве примечания: a.(MyValue).Value = ... не компилируется, поскольку a.(MyValue).Valueне адресуется.

*MyStruct реализует encoding.TextUnmarshaler, так что попробуйте

var ms any = &MyStruct{value: "hello"}

Редактировать:

Если вы попробуете какой-нибудь трюк, вас ждет сюрприз (Go Playground):

package main

import (
    "encoding"
    "reflect"
    "testing"

    "github.com/stretchr/testify/assert"
)

type MyStruct struct{ value string }

var _ encoding.TextMarshaler = (*MyStruct)(nil)

func (id MyStruct) MarshalText() (text []byte, err error) {
    return []byte(id.value), nil
}

var _ encoding.TextUnmarshaler = (*MyStruct)(nil)

func (id *MyStruct) UnmarshalText(data []byte) error {
    *id = MyStruct{value: string(data)}

    return nil
}

func TestIfICouldInvokeUnmarshalText(t *testing.T) {
    var ms any = MyStruct{value: "hello"}
    _, ok := (ms).(encoding.TextMarshaler)
    if !ok {
        t.FailNow()
    }
    _, ok = (ms).(encoding.TextUnmarshaler)
    assert.False(t, ok)

    v := reflect.Indirect(reflect.New(reflect.TypeOf(ms)))
    v.Set(reflect.ValueOf(ms)) // makes an addressable copy
    sp := v.Addr().Interface()

    u, ok := (sp).(encoding.TextUnmarshaler)
    if !ok {
        t.FailNow()
    }

    _ = u.UnmarshalText([]byte("world"))

    m, _ := ms.(MyStruct)
    assert.Equal(t, "world", m.value)
}

Да, вы можете вызвать UnmarshalText для копии вашей структуры, но результаты будут потеряны, и ваша исходная переменная по-прежнему будет содержать «привет» вместо «мир».

Я думаю, что вы хотите сделать что-то вроде этого:

var ms any = MyStruct{value: "hello"}
_, ok := (ms).(encoding.TextMarshaler)
if !ok {
    t.FailNow()
}
_, ok = (ms).(encoding.TextUnmarshaler)
//assert.True(t, ok) // this is the first attempt, which will fail
v := reflect.Indirect(reflect.New(reflect.TypeOf(ms)))
v.Set(reflect.ValueOf(ms))
sp := v.Addr().Interface()
_, ok = (sp).(encoding.TextUnmarshaler)
if !ok {
    t.FailNow()
}

Это не делает то, что вы думаете. Вы вызываете TextUnmarshaler не на «этом», а на копии (Go Playgound).

eik 26.06.2024 11:20

Другие вопросы по теме