Я пытаюсь осознать тот факт, что утверждение типа Golang работает только для переменных, которые явно определены как тип интерфейса, и не работает для конкретных типов (например, «string», «int32» и т. д. .).
Вот быстрый и простой пример кода, который иллюстрирует основную причину моего замешательства:
package main
import "fmt"
// here we define an interface type:
type Shape interface {
DoubleSize() int32
}
// here we define a new type which is really just an int32:
type Rect int32
// here we make "Rect" type above comply with the "Shape" interface by implementing the methods of that interface
// and so, since the interfaces in Go are implemented implicitly, this should make the "Rect" type an implicit instance of the "Shape" interface
func (theShape Rect) DoubleSize() int32 {
return int32(theShape) * 2
}
// this function expects its "someShape" parameter to be of "Shape" type (or "the type that quacks like "Shape" interface does))
func whateverFunction(someShape Shape) int32 {
return someShape.DoubleSize()
}
func main() {
var newRect = Rect(5)
// ^^ if this is instead written as "var newRect Shape = Rect(5)" no error with type assertion happens down the line
whateverFunction(newRect) // the function works just fine because "newRect" implicitly implements the "Shape" interface — the one that this function expects to receive.
// !! but type assertion doesn't work on "newRect"
v, ok := newRect.(Shape) // error: invalid operation: newRect (variable of type Rect) is not an interface
if !ok {
fmt.Println("type assertion failed")
return
}
fmt.Println("This is v:", v)
}
Как следует из названия этого вопроса, я не могу понять причину реализации утверждения типа для работы только с типами интерфейса и проверить, является ли базовое значение, присвоенное переменной, которая ЯВНО реализует этот интерфейс, тем, что мы указываем внутри ".( Т)" метод утверждения. Это заставляет меня чувствовать, что «утверждение типа» является непреднамеренным неправильным употреблением, которое подразумевает, что оно работает для всех типов, но не работает только для типов интерфейса.
Я имею в виду, что, очевидно, должна быть причина, стоящая за этим решением по дизайну языка, и я думаю, что это может иметь какое-то отношение к тому, как должен быть написан идиоматический Golang, но, хотя я видел много ресурсов по этому вопросу, они никогда не уточняют эта причина.
Причина, которая имеет для меня некоторый смысл, заключается в том, что программа Go должна «предпочтительно (я предполагаю, поскольку явное определение интерфейса не является обязательным)» быть написана со всеми переменными, представляющими некоторый интерфейс (поведение), и, следовательно, определяя явный интерфейс на переменная имеет смысл для ясности и удобочитаемости.
Но, как я уже упоминал, я никогда не видел, чтобы какие-либо ресурсы указывали, почему именно так реализована функциональность «утверждения типа» в Go, и я надеюсь, что вы поможете мне прояснить эту путаницу.
-- upd 1 - Добавление немного, чтобы прояснить мой вопрос:
По сути, я думаю, мой вопрос касается причины (которую я не понимаю) утверждение типа работает только тогда, когда интерфейс переменной реализован явно, но не когда интерфейс реализован неявно.
Как показано в «whateverFunction», код рассматривает «newRect» для реализации интерфейса «Shape» или «будет реализацией интерфейса «Shape»» (иначе функция не работала бы с этой переменной, но она работает). ), но код метода утверждения типа ".(T)" не считает "newRect" реализацией интерфейса "Shape".
Поэтому, если в Golang есть различия в том, что считать реализацией интерфейса, я подумал, что должна быть причина такого дизайнерского решения (дифференцировать).
И именно поэтому я упомянул, что единственная причина, о которой я пока мог думать, это то, что это был способ заставить людей писать код Go определенным образом.
Утверждение типа проверяет тип значения, содержащегося в интерфейсе. Вот как это определяется:
https://go.dev/ref/spec#Type_assertions
Это связано с тем, что интерфейс содержит два значения: тип значения и фактическое значение, а проверка того, относится ли значение, содержащееся в интерфейсе, к определенному типу, является операцией времени выполнения.
Утверждение типа не определено для неинтерфейсного значения, поскольку тип такого значения уже известен и не может быть другим типом.
Вы можете использовать утверждение типа, чтобы проверить, реализует ли интерфейс другой интерфейс. Это означает, что конкретное значение, содержащееся в интерфейсе, также реализует интерфейс с установленным типом.
Теперь вернемся к тому, что вы пытаетесь сделать:
v, ok := newRect.(Shape)
Это просто:
v:=Shape(newRect)
Потому что, если newRect
не реализует интерфейс Shape
, это выражение является ошибкой времени компиляции, в отличие от обнаружения подтверждения типа во время выполнения.
Утверждение типа не проверяет, реализует ли тип интерфейс. Это можно сделать во время компиляции, как я показал выше. Утверждение типа проверяет, имеет ли интерфейс определенный тип. Это невозможно сделать во время компиляции.
Как я упоминал в своем вопросе изначально, я знаю, что утверждения типа работают только с типом интерфейса и проверяют базовый тип явной реализации интерфейса. Мой вопрос заключается в том, что может быть причиной дизайнерского решения заставить его работать только с типами, которые явно определены как интерфейсные, а не те, которые также считаются относящимися к этому типу интерфейса, но неявно. «whateverFunction» говорит «да, это Shape», утверждение типа не видит, что «newRect» неявно является Shape, и говорит «нет, это не Shape». Почему язык был разработан таким образом?
Потому что то, что вы просите, делается во время компиляции. Утверждение типа выполняется во время выполнения.
Вы можете ознакомиться с ответом Бурака Сердара - он может оказаться более кратким и полезным. Тем не менее, я опубликую всю цепочку рассуждений, которые заставили меня наконец *щелкнуть*:
|-> Интерфейсы используются, когда мы не можем быть уверены в точном типе данных, которые ожидаем получить (поскольку, например, в результате пользовательского ввода один и тот же параметр в какой-то функции в программе может получить данные разных типов), но мы знаем точное поведение, которое должны иметь предоставленные данные.
^^ Из этого следует, что фактический тип значения, которое будет храниться в интерфейсе, во время компиляции неизвестен. (В противном случае, очевидно, мы бы просто указали это прямо в коде.)
| -> Поэтому нам дается утверждение типа, чтобы иметь возможность определять поведение программы на основе возможных значений, которые, как мы ожидаем, будут предоставлены программе во время ее выполнения.
|-> Поэтому причина, по которой утверждение типа работает только с переменными, явно указанными как тип интерфейса, а не с теми, которые могут реализовывать тот же интерфейс, но явно не указаны как интерфейсный тип
это потому что
нам нужно такое утверждение типа только во время выполнения, когда мы используем интерфейсы, потому что мы не знаем точный тип данных, которые будут отправлены в программу - это необходимость, которая возникает только при использовании интерфейсов, поэтому утверждение типа работает только для типы, явно указанные как типы интерфейса, потому что во всех других случаях тип данных известен (что позволяет компилятору неявно предполагать реализацию интерфейса с помощью переменной, потому что он уже знает все типы данных задействованных данных), и мы просто никогда не нужно использовать утверждение типа с данными, тип которых уже известен.
Пожалуйста, смотрите «upd 1» на мой вопрос, он был слишком подробным, чтобы опубликовать его здесь в качестве комментария.