Typecasting в Golang - PullRequest
       23

Typecasting в Golang

0 голосов
/ 27 февраля 2020

Я читал следующую статью: https://www.ribice.ba/golang-enums/

В одном из примеров кода определена функция:

func (lt *LeaveType) UnmarshalJSON(b []byte) error {
    // Define a secondary type to avoid ending up with a recursive call to json.Unmarshal
    type LT LeaveType;
    var r *LT = (*LT)(lt);
    err := json.Unmarshal(b, &r)
    if err != nil{
        panic(err)
    }
    switch *lt {
    case AnnualLeave, Sick, BankHoliday, Other:
        return nil
    }
    return errors.New("Inalid leave type")
}

Что такое синтаксис var r *LT = (*LT)(lt); делает в этом примере?

Ответы [ 5 ]

6 голосов
/ 27 февраля 2020

Go технически не имеет приведений , а скорее преобразований . Синтаксис для явного преобразования: T(x), где T - некоторый тип, а x - некоторое значение, которое может быть преобразовано в этот тип. Подробнее см. Преобразования в спецификации Go .

Как видно из объявления функции:

func (lt *LeaveType) UnmarshalJSON(b []byte) error {

lt сам по себе имеет тип указатель на LeaveType и UnmarshalJSON является функцией приемника для типа *LeaveType. Пакет encoding/json будет вызывать такую ​​функцию для декодирования ввода JSON, когда переменная, которую пакет хочет установить, имеет тип LeaveType (или *LeaveType - в этом случае пакет сам создаст переменную LeaveType ).

Как говорится в комментарии в коде, автор кода теперь хотел бы, чтобы код encoding/json отменял маршализацию JSON , как если бы не было функцией UnmarshalJSON. Но - это функция UnmarshalJSON, поэтому, если мы просто вызовем код encoding/json без небольшой хитрости, encoding/json просто вызовет эту функцию снова, что приведет к бесконечной рекурсии.

Определяя новый тип LT, содержимое которого точно совпадает с существующим типом LeaveType, мы получаем новый тип, который не не имеет получателя функция. Вызов encoding/json для экземпляра этого типа (или указателя на этот тип) не вызовет приемник *LeaveType, потому что LT - это другой тип, даже если содержимое точно соответствует.

Мы могли бы сделать это:

func (lt *LeaveType) UnmarshalJSON(b []byte) error {
    type LT LeaveType
    var r LT
    err := json.Unmarshal(b, &r)
    if err != nil {
        panic(err)
    }
    // ...
}

Это заполнит r, который имеет тот же размер и форму, что и любая переменная LeaveType , Тогда мы могли бы использовать заполненный r, чтобы установить *lt:

*lt = LeaveType(r) // an ordinary conversion

, после чего мы могли бы продолжать идти как прежде, используя *lt в качестве значения. Но это означает, что UnmarshalJSON пришлось установить временную переменную r, которую мы затем должны были скопировать в конечный пункт назначения. Почему бы вместо этого не настроить что-то так, чтобы UnmarshalJSON заполняло целевую переменную, но используя выбранный нами тип?

Вот каков здесь синтаксис для . Это не самая короткая версия 1076 *: как отметил Cerise Limón, существует более короткий способ его написания (и это более короткое написание обычно предпочтительнее). Первый набор скобок в (*LT)(lt) требуется для привязки * - указатель к части - к LT, так как *LT(lt) имеет неправильную привязку: это означает то же самое, что и *(LT(lt)) это не то, что мы хотим.

6 голосов
/ 27 февраля 2020

Выражение (*LT)(lt) представляет собой преобразование в тип *LT.

Оператор var r *LT = (*LT)(lt); объявляет переменную r как тип *LT с начальным значением (*LT)(lt). Заявление можно записать более просто как r := (*LT)(lt). Нет необходимости указывать тип дважды или заканчивать строку точкой с запятой.

Функция объявляет тип LT с пустым методом, установленным, чтобы избежать рекурсивного вызова UnMarshalJSON.

4 голосов
/ 27 февраля 2020

json.Unmarshal() отменяет маршалирование некоторого JSON текста в значение Go. Если значение для отмены маршалирования реализует интерфейс json.Unmarshaler, вызывается его метод UnmarshalJSON(), который позволяет реализовать пользовательские логи демаршалинга c.

Цитирование из json.Unmarshal():

Чтобы демаршировать JSON в значение, реализующее интерфейс Unmarshaler, Unmarshal вызывает метод Unmarshal JSON этого значения, в том числе, когда входное значение равно JSON. * Интерфейс json.Unmarshaler:

type Unmarshaler interface {
    UnmarshalJSON([]byte) error
}

LeaveType (или, более конкретно, *LeaveType) имеет метод UnmarshalJSON(), который мы можем видеть в вопросе, поэтому он реализует json.Unmarshaler.

И метод LeaveType.UnmarshalJSON() хочет использовать default unmarshaling logi c, который выполняет "трудную" часть и просто хочет внести некоторые окончательные корректировки. Поэтому он вызывает json.Unmarshal():

err := json.Unmarshal(b, &r)

Если мы передадим lt для демаршалирования, -since lt внедряет json.Unmashaler - LeaveType.UnmarshalJSON() будет вызываться из пакета json, эффективно вызывая бесконечную «рекурсию».

Конечно, это не то, что мы хотим. Чтобы избежать бесконечной рекурсии, мы должны передать значение, которое не реализует json.Unmarshaler, значение, тип которого не имеет метода UnmarshalJSON().

Здесь начинается создание нового типа. картинка:

type LT LeaveType

Ключевое слово type создает новый тип с именем LT, отличный от LeaveType. Он не «наследует» ни один из LeaveType методов, поэтому LT не реализует json.Unmarshaler. Поэтому, если мы передадим значение LT или *LT в json.Unmarshal(), это не приведет к вызову LeaveType.UnmarshalJSON() (пакетом json).

var r *LT = (*LT)(lt)

Это объявляет переменная с именем r, тип которой *LT. И это присваивает значение lt, преобразованное в *LT. Преобразование необходимо, потому что lt имеет тип *LeaveType, поэтому его нельзя назначить переменной типа *LT, но поскольку LT имеет LeaveType в качестве базового типа, *LeaveType может быть преобразовано в *LT.

Таким образом, r является указателем, он указывает на то же значение, что и lt, он имеет ту же структуру памяти. Поэтому, если мы используем логику демаршалинга по умолчанию c и «заполняем» структуру, указанную r, то будет заполнена «та же» структура, указанная lt.

См. Связанный / похожий вопрос: Вызов json. Unmarshal внутри функции Unmarshal JSON без переполнения стека

3 голосов
/ 27 февраля 2020

Он приводит lt, указатель LeaveType к указателю LT.

LT определяется чуть выше type LT LeaveType;, что эквивалентно LeaveType.

Он делает это по причинам, объясненным в комментарии.

// Define a secondary type to avoid ending up with a recursive call to json.Unmarshal

Является ли это эффективным или необходимым, я не знаю.

1 голос
/ 27 февраля 2020

Вы можете увидеть тот же эффект в игре на простом примере интерфейса Stringer, где функция fmt.Println попытается упорядочить данные в формате string. Если у типа заданного значения есть метод String(), он будет использоваться вместо отражения.

Эта реализация не выполняется (и go vet выдает предупреждение), поскольку она вызывает бесконечную рекурсию:

type mystring string
func (ms mystring) String() string {
    return fmt.Sprintf("mystring: %s", ms)
}

Эта версия важна для исходного кода:

type mystring2 string

func (ms mystring2) String() string {
    type mystring2 string // <- local type mystring2 overrides global type
    v := mystring2(ms)

    return fmt.Sprintf("mystring2: %s", v)
}

Удалите строку type mystring2 string и посмотрите, что произойдет .

...