Подтвердить что ты не робот

Объясните утверждения типа в Go

Я читаю об утверждениях типа x.(T) на языке программирования Go и не понимаю их.

Я понимаю, что есть разные сценарии:

  • T - это конкретный тип или интерфейс
  • Одно (заявленное значение?) Или два (нормально) значения могут быть возвращены

Вот чего я не понимаю:

  • Зачем мне их использовать?
  • Что именно они возвращают?

Я также гуглил по теме и до сих пор не понимаю.

4b9b3361

Ответ 1

Короткий ответ

В одну строку:

x.(T) утверждает, что x не равен nil и что значение, хранящееся в x имеет тип T

Зачем мне их использовать:

  • проверить x ноль
  • проверить, может ли он быть преобразован (утверждать) в другой тип
  • конвертировать (утверждать) в другой тип

Что именно они возвращают:

  • t := x.(T) => t относится к типу T; если x ноль, это паника.

  • t,ok := x.(T) => если x равен нулю или не типа T => ok является false в противном случае ok является true, и t имеет тип T.


Детальное объяснение

Представьте, что вам нужно рассчитать площадь 4 различных форм: круг, квадрат, прямоугольник и треугольник. Вы можете определить новые типы с помощью нового метода Area(), например:

type Circle struct {
    Radius float64
}
func (t Circle) Area() float64 {
    return math.Pi * t.Radius * t.Radius
}

И для Triangle:

type Triangle struct {
    A, B, C float64 // lengths of the sides of a triangle.
}
func (t Triangle) Area() float64 {
    p := (t.A + t.B + t.C) / 2.0 // perimeter half
    return math.Sqrt(p * (p - t.A) * (p - t.B) * (p - t.C))
}

И для Rectangle:

type Rectangle struct {
    A, B float64
}

func (t Rectangle) Area() float64 {
    return t.A * t.B
}

И для Square:

type Square struct {
    A float64
}
func (t Square) Area() float64 {
    return t.A * t.A
}

Здесь у вас есть Circle с радиусом 1,0 и другие фигуры с их сторонами:

shapes := []Shape{
    Circle{1.0},
    Square{1.772453},
    Rectangle{5, 10},
    Triangle{10, 4, 7},
}

Интересно! Как мы можем собрать их всех в одном месте?
Для начала вам нужен Shape interface чтобы собрать их все в один фрагмент shape []Shape:

type Shape interface {
    Area() float64
}

Теперь вы можете собрать их так:

shapes := []Shape{
    Circle{1.0},
    Square{1.772453},
    Rectangle{5, 10},
    Triangle{10, 4, 7},
}

В конце концов, Circle - это Shape и Triangle - это тоже Shape.
Теперь вы можете напечатать область каждой фигуры, используя единственный оператор v.Area():

for _, v := range shapes {
    fmt.Println(v, "\tArea:", v.Area())
}

Таким образом, Area() - это общий интерфейс между всеми фигурами. Теперь, как мы можем вычислить и вызвать необычный метод, такой как углы треугольника, используя вышеуказанные shapes?

func (t Triangle) Angles() []float64 {
    return []float64{angle(t.B, t.C, t.A), angle(t.A, t.C, t.B), angle(t.A, t.B, t.C)}
}
func angle(a, b, c float64) float64 {
    return math.Acos((a*a+b*b-c*c)/(2*a*b)) * 180.0 / math.Pi
}

Теперь пришло время извлечь Triangle из вышеперечисленных shapes:

for _, v := range shapes {
    fmt.Println(v, "\tArea:", v.Area())
    if t, ok := v.(Triangle); ok {
        fmt.Println("Angles:", t.Angles())
    }
}

Используя t, ok := v.(Triangle) мы запросили утверждения типа, то есть мы попросили компилятор попытаться преобразовать v типа Shape в тип Triangle, чтобы в случае успеха, ok бы true противном случае - false, а затем, если это успешно вызов t.Angles() чтобы вычислить три угла треугольника.

Это вывод:

Circle (Radius: 1)  Area: 3.141592653589793
Square (Sides: 1.772453)    Area: 3.1415896372090004
Rectangle (Sides: 5, 10)    Area: 50
Triangle (Sides: 10, 4, 7)  Area: 10.928746497197197
Angles: [128.68218745348943 18.194872338766785 33.12294020774379]

И весь рабочий пример кода:

package main

import "fmt"
import "math"

func main() {
    shapes := []Shape{
        Circle{1.0},
        Square{1.772453},
        Rectangle{5, 10},
        Triangle{10, 4, 7},
    }
    for _, v := range shapes {
        fmt.Println(v, "\tArea:", v.Area())
        if t, ok := v.(Triangle); ok {
            fmt.Println("Angles:", t.Angles())
        }
    }
}

type Shape interface {
    Area() float64
}
type Circle struct {
    Radius float64
}
type Triangle struct {
    A, B, C float64 // lengths of the sides of a triangle.
}
type Rectangle struct {
    A, B float64
}
type Square struct {
    A float64
}

func (t Circle) Area() float64 {
    return math.Pi * t.Radius * t.Radius
}

// Heron Formula for the area of a triangle
func (t Triangle) Area() float64 {
    p := (t.A + t.B + t.C) / 2.0 // perimeter half
    return math.Sqrt(p * (p - t.A) * (p - t.B) * (p - t.C))
}
func (t Rectangle) Area() float64 {
    return t.A * t.B
}

func (t Square) Area() float64 {
    return t.A * t.A
}

func (t Circle) String() string {
    return fmt.Sprint("Circle (Radius: ", t.Radius, ")")
}
func (t Triangle) String() string {
    return fmt.Sprint("Triangle (Sides: ", t.A, ", ", t.B, ", ", t.C, ")")
}
func (t Rectangle) String() string {
    return fmt.Sprint("Rectangle (Sides: ", t.A, ", ", t.B, ")")
}
func (t Square) String() string {
    return fmt.Sprint("Square (Sides: ", t.A, ")")
}

func (t Triangle) Angles() []float64 {
    return []float64{angle(t.B, t.C, t.A), angle(t.A, t.C, t.B), angle(t.A, t.B, t.C)}
}
func angle(a, b, c float64) float64 {
    return math.Acos((a*a+b*b-c*c)/(2*a*b)) * 180.0 / math.Pi
}

Также см:

Введите утверждения

Для выражения x типа интерфейса и типа T основное выражение

x.(T)  

утверждает, что x не равен nil и что значение, хранящееся в x, имеет тип T. Обозначение x. (T) называется утверждением типа.

Точнее, если T не является интерфейсным типом, x. (T) утверждает, что динамический тип x идентичен типу T. В этом случае T должен реализовать тип (interface) x; в противном случае утверждение типа недопустимо, поскольку для x невозможно сохранить значение типа T. Если T является типом интерфейса, x. (T) утверждает, что динамический тип x реализует интерфейс T.

Если утверждение типа выполнено, значением выражения является значение, сохраненное в x, а его типом является T. Если утверждение типа равно false, возникает паника во время выполнения. Другими словами, даже если динамический тип x известен только во время выполнения, тип x. (T) известен как T в правильной программе.

var x interface{} = 7  // x has dynamic type int and value 7
i := x.(int)           // i has type int and value 7

type I interface { m() }
var y I
s := y.(string)        // illegal: string does not implement I (missing method m)
r := y.(io.Reader)     // r has type io.Reader and y must implement both I and io.Reader

Утверждение типа, используемое в присваивании или инициализации специальной формы

v, ok = x.(T)
v, ok := x.(T)
var v, ok = x.(T)

дает дополнительное нетипизированное логическое значение. Значение ok равно true, если утверждение верно. В противном случае оно равно false, а значение v является нулевым значением для типа T. В этом случае не возникает паника во время выполнения.


РЕДАКТИРОВАТЬ

Вопрос: Что возвращает утверждение x.(T) когда T является interface{} а не конкретным типом?
Ответ:

Он утверждает, что x не равен nil и что значение, хранящееся в x, имеет тип T.

Например, эта паника (компиляция: успех, запуск: panic: interface conversion: interface is nil, not interface {}):

package main

func main() {
    var i interface{} // nil
    var _ = i.(interface{})
}

И это работает (Run: OK):

package main

import "fmt"

func main() {
    var i interface{} // nil
    b, ok := i.(interface{})
    fmt.Println(b, ok) // <nil> false

    i = 2
    c, ok := i.(interface{})
    fmt.Println(c, ok) // 2 true

    //var j int = c // cannot use c (type interface {}) as type int in assignment: need type assertion
    //fmt.Println(j)
}

Выход:

<nil> false
2 true

ПРИМЕЧАНИЕ: здесь c имеет тип interface {} а не int.


Посмотрите этот рабочий пример кода с закомментированными выводами:

package main

import "fmt"

func main() {
    const fm = "'%T'\t'%#[1]v'\t'%[1]v'\t%v\n"
    var i interface{}
    b, ok := i.(interface{})
    fmt.Printf(fm, b, ok) // '<nil>'    '<nil>' '<nil>' false

    i = 2
    b, ok = i.(interface{})
    fmt.Printf(fm, b, ok) // 'int'  '2' '2' true

    i = "Hi"
    b, ok = i.(interface{})
    fmt.Printf(fm, b, ok) // 'string'   '"Hi"'  'Hi'    true

    i = new(interface{})
    b, ok = i.(interface{})
    fmt.Printf(fm, b, ok) // '*interface {}'    '(*interface {})(0xc042004330)' '0xc042004330'  true

    i = struct{}{}
    b, ok = i.(interface{})
    fmt.Printf(fm, b, ok) // 'struct {}'    'struct {}{}'   '{}'    true

    i = fmt.Println
    b, ok = i.(interface{})
    fmt.Printf(fm, b, ok) // 'func(...interface {}) (int, error)'   '(func(...interface {}) (int, error))(0x456740)'    '0x456740'  true

    i = Shape.Area
    b, ok = i.(interface{})
    fmt.Printf(fm, b, ok) // 'func(main.Shape) float64' '(func(main.Shape) float64)(0x401910)'  '0x401910'  true
}

type Shape interface {
    Area() float64
}

Ответ 2

Обычная usecase: проверьте, была ли возвращенная ошибка типа T.

https://golang.org/ref/spec#Type_assertions

Для одного утверждения возвращаемого значения: при сбое программы паники.

Для двух значений возвращаемых значений: когда он терпит неудачу, второй аргумент имеет значение false, и программа не вызывает панику.

Ответ 3

Утверждение типа является обозначением x. (T), где x имеет тип интерфейса, а T является типом. Кроме того, фактическое значение, хранящееся в x, имеет тип T, и T должен удовлетворять типу интерфейса x.