#17.2 Пустой интерфейс - какой угодно тип (тип any)

Начиная с версии Go 1.18, появился тип any, он является синонимом на interface{} (пустой интерфейс).

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

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

package main

import "fmt"

func main() {
   fmt.Print(1, 1.21, "hi")
}

-- в этом простом пример функция fmt.Print() принимает любой тип данных (any) и проверяет, какое значение мы передали, чтобы его вывести.
При выполнении программы будет выведено что-то вроде:

1 1.21hi

Рассмотрим более сложный пример, где добавим к структуре метод:

type Wallet struct {
	Cash int
}

// Навесим метод со стандартным именем
// ответчающим за строковое предлставление%:
func (w *Wallet) String() string {
	return "Остаток на счете " +
		strconv.Itoa(w.Cash) + " рублей"
}

func main() {
	wall := &Wallet{
		Cash: 10,
	}

	fmt.Print(wall)
}

При выполнении программы будет выведено:

Остаток на счете 10 рублей.

-- это так потому, что если переданный в тип реализует interface fmt.Stringer, то результат вывода будет определяться за счет выполнения метода String.

Преобразование any к конкретному типу

Расмотрим преобразование типа any к интерфейсу, сделаем этон а примере функции Buy3(), написанной по аналогии с примерами Buy() и Buy2() из урока по интерфейсам:

// Обший интерфес "Платильщик"
type Payer interface {
	pay(int) error   // что-то оплачиваем
	getBalance() int // узнаем сколько денег еще осталось
}

type Wallet struct {
	cash int
}

// навешиваем метод на структуру
func (w *Wallet) pay(amount int) error {
	if w.cash < amount {
		return fmt.Errorf("не хватает денег в кошельке")
	}
	w.cash -= amount
	return nil
}

func (w *Wallet) getBalance() int {
	return w.cash
}

// Структура описывающая банковскую карточку
type Card struct {
	Balance    int
	ValidUntil string
	Cardholder string
	CVV        string
	Number     string
}

func (c *Card) pay(amount int) error {
	if c.Balance < amount {
		return fmt.Errorf("не хватает денег на карте")
	}
	c.Balance -= amount
	return nil
}

func (c *Card) getBalance() int {
	return c.Balance
}

// Платежные аккаунт в какой-то системе
type KtuPay struct {
	money int
	ktuId string
}

func (a *KtuPay) pay(amount int) error {
	if a.money < amount {
		return fmt.Errorf("не хватает денег на аккаунте")
	}
	a.money -= amount
	return nil
}

func (kp *KtuPay) getBalance() int {
	return kp.money
}

// Покупка с проверкой типа платежного средства
func Buy3(in any, amount int) {
	var (
		p  Payer
		ok bool
	)

	// Проверяем соответсвует ли перменная in типу Payer
	p, ok = in.(Payer)
	if !ok {
		fmt.Printf("\n %T не является платежным средством\n\n", in)
		return
	}

	if err := p.pay(amount); err != nil {
		fmt.Printf("Ошибка при оплате %T: %v\n\n", p, err)
		return
	}

	fmt.Printf("Спасибо за покупку через %T\n", p)
}

// Произведем оплату разными средствами
func main() {
	myWallet := &Wallet{cash: 100}
	Buy3(myWallet, 10)
	fmt.Println("Осталось монет:", myWallet.getBalance())

	var myMoney Payer
	myMoney = &Card{Balance: 80, Cardholder: "rvasily"}
	Buy3(myMoney, 10)
	fmt.Println("Осталось монет:", myMoney.getBalance())

	myMoney = &KtuPay{money: 70}
	Buy3(myMoney, 10)
	fmt.Println("Осталось монет:", myMoney.getBalance())

	// А вот строкой заплатить не получится:
	Buy3("Привет!", 10)
}

-- в функцию Buy3() можно передавать вообще любой тип данных, если этот тип будет соотвествовать интерфейсу Payer, то будет произведена оплата, если запустить этот код, то получим распечатку:

Спасибо за покупку через *main.Wallet
Осталось монет: 90
Спасибо за покупку через *main.Card
Осталось монет: 70
Спасибо за покупку через *main.KtuPay
Осталось монет: 60

 string не является платежным средством



Проверка, скрывающегося под any - type switch

По аналогии ситуацией, когда мы уточняли какой именно тип скрывается под обычным (непустым) интерфейсом, с помощью type switch мы мы можем проверять соответствие другим более конкретным типам, если аргумент объявлен как any:"

Рассмотрим пример:


type Wallet struct {
	cash int
}

func GoTypes(in any) {
	switch typed := in.(type) {
	case string:
		fmt.Println("Обработали строку", typed)
	case int:
		fmt.Println("Обработали число", typed)
	default:
		fmt.Printf("Функция GoTypes не умеет обрабатывать: %T", in)
	}
}

func main() {
	GoTypes("hello")
	GoTypes(1)
	GoTypes(&Wallet{cash: 10})
}

-- после запуска получим распечатку:

Обработали строку hello
Обработали число 1
Функция GoTypes не умеет обрабатывать: *main.Wallet

Key Words for FKN + antitotal forum (CS VSU):