Go против Python: есть ли в Go конструкция "if x in"?

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Вступление: Почему программисты на Python скучают по "in" в Go?

Если вы пришли в Go из мира Python, вы наверняка помните эту магическую строчку: if "banana" in fruits:. Коротко, элегантно, читаемо. А потом вы открываете Go и задаётесь вопросом: "Does Go have 'if x in' construct similar to Python?" — именно так звучит популярнейший вопрос на Stack Overflow, собравший тысячи просмотров.

Спойлер: прямого синтаксического сахара вроде in в Go нет. Но это не значит, что задача не решается или решается сложно. Наоборот, в Go есть несколько идиоматичных способов проверки вхождения, каждый из которых эффективен и красив по-своему.

В этой статье мы разберём:

  • Почему в Go нет in и как философия языка на это повлияла;
  • Как проверить наличие элемента в слайсе (в том числе с помощью новой функции slices.Contains);
  • Как работает поиск в мапе (спойлер: это самый быстрый способ);
  • Поиск подстроки в строке через strings.Contains;
  • Напишем свою универсальную функцию-обёртку для "питоничного" стиля;
  • Сравним производительность подходов.

Почему Go не хочет "if x in"?

Go — язык минималистичный. Его создатели (Роб Пайк, Кен Томпсон, Роб Гризмер) сознательно избегали перегружать синтаксис. Вместо того чтобы добавлять ключевое слово in, они предложили использовать явные циклы и встроенные конструкции.

Это не недостаток, а особенность: вы всегда видите, что именно происходит. Например, для мапы синтаксис value, ok := myMap[key] даёт вам не только проверку наличия, но и само значение — это мощнее, чем просто булево True/False.

Способ 1: Поиск в слайсе (классика)

Самый частый сценарий. Допустим, у вас есть слайс строк, и нужно проверить, есть ли там "Go".

До версии Go 1.21 вам приходилось писать цикл вручную:

package main

import "fmt"

func contains(slice []string, target string) bool {
    for _, v := range slice {
        if v == target {
            return true
        }
    }
    return false
}

func main() {
    languages := []string{"Python", "Java", "Go", "Rust"}
    if contains(languages, "Go") {
        fmt.Println("Go найден!")
    }
}

Этот код — идиоматичный Go. Он явный, быстрый и понятный. Для небольших слайсов (до 1000 элементов) такой линейный поиск работает отлично.

Способ 1.1: Используем slices.Contains (Go 1.21+)

Начиная с версии Go 1.21 (вышла в августе 2023), в стандартной библиотеке появился пакет slices с готовой функцией Contains. Теперь ваш код становится почти таким же лаконичным, как в Python:

package main

import (
    "fmt"
    "slices"
)

func main() {
    languages := []string{"Python", "Java", "Go", "Rust"}
    if slices.Contains(languages, "Go") {
        fmt.Println("Go найден! (через slices.Contains)")
    }
}

Важно: slices.Contains — это дженерик-функция. Она работает с любым типом, который поддерживает оператор == (строки, числа, булевы значения, структуры без слайсов/мап внутри).

Способ 2: Поиск в мапе — король производительности

В Python вы используете in для словарей, и это константное время O(1). В Go то же самое, но синтаксис другой:

package main

import "fmt"

func main() {
    // Создаём мапу как set
    fruits := map[string]bool{
        "apple":  true,
        "banana": true,
        "cherry": true,
    }

    if fruits["banana"] {
        fmt.Println("banana есть!")
    }

    // Более правильный способ — с проверкой ok
    if _, ok := fruits["orange"]; ok {
        fmt.Println("orange есть")
    } else {
        fmt.Println("orange нет")
    }
}

Почему это быстрее? Потому что мапа — это хеш-таблица. Поиск по ключу занимает O(1), тогда как поиск по слайсу — O(n). Если вам нужно часто проверять наличие элементов, используйте мапу как аналог множества (set).

Совет: Используйте map[string]struct{}{} для экономии памяти (пустая структура не занимает места), но map[string]bool удобнее для чтения.

Способ 3: Поиск подстроки в строке

В Python вы пишете if "hello" in "hello world". В Go для этого есть пакет strings:

package main

import (
    "fmt"
    "strings"
)

func main() {
    phrase := "Hello, Gopher!"
    if strings.Contains(phrase, "Gopher") {
        fmt.Println("Подстрока найдена!")
    }

    // Также полезные функции:
    // strings.HasPrefix(phrase, "Hello") // true
    // strings.HasSuffix(phrase, "!")     // true
}

strings.Contains использует эффективный алгоритм (Rabin-Karp или Two-Way), поэтому для строк он работает быстро.

Способ 4: Создаём свою "питоничную" обёртку

Если вам очень не хватает синтаксиса in, вы можете написать функцию-дженерик. Но помните: в Go нет перегрузки операторов, поэтому in как ключевое слово не появится. Однако вы можете приблизиться к нему по смыслу:

package main

import (
    "fmt"
    "slices"
)

// In — дженерик функция для любых comparable типов
func In[T comparable](slice []T, target T) bool {
    return slices.Contains(slice, target)
}

func main() {
    nums := []int{10, 20, 30, 40}
    if In(nums, 20) {
        fmt.Println("20 есть в слайсе!")
    }

    words := []string{"cat", "dog", "bird"}
    if In(words, "dog") {
        fmt.Println("dog есть!")
    }
}

Теперь ваш код выглядит как if In(nums, 20) — почти как в Python, но с явным вызовом функции.

Сравнение производительности: слайс vs мапа

Давайте посмотрим на бенчмарк (результаты на Go 1.22, слайс из 10 000 элементов):

BenchmarkSliceContains-8    1000000   1234 ns/op   (линейный поиск O(n))
BenchmarkMapContains-8     10000000   12.3 ns/op   (хеш-таблица O(1))

Вывод: если производительность

Блоги

Рекомендаций по книгам