Go для полного новичка: от основ до первой программы

Курс познакомит с языком Go с нуля: синтаксис, типы данных, функции, структуры и основы конкурентности. В конце вы сможете писать небольшие консольные приложения, работать с файлами и понимать базовые принципы разработки на Go.

1. Подготовка: установка Go, инструменты и первая программа

Подготовка: установка Go, инструменты и первая программа

В этой статье вы подготовите окружение для разработки на Go: установите язык, проверите, что всё работает, настроите редактор и запустите первую программу. Это база, на которую будут опираться все следующие уроки.

Что такое Go и что нужно для старта

Go (или Golang) — компилируемый язык программирования. Для начала вам нужны:

  • Установленный Go (компилятор и стандартные инструменты)
  • Терминал (командная строка)
  • Редактор кода
  • Остальное (библиотеки, зависимости) Go умеет подтягивать сам.

    Установка Go

    Официальный сайт и загрузки:

  • Страница загрузки Go
  • Официальная инструкция установки
  • Windows

  • Скачайте установщик (Windows MSI installer) с страницы загрузки Go.
  • Запустите установщик и завершите установку с настройками по умолчанию.
  • Откройте PowerShell или Command Prompt и выполните команду:
  • Если вы видите версию (например, go version go1.xx.x windows/amd64), значит установка прошла успешно.

    macOS

    Способ 1 (официальный пакет):

  • Скачайте macOS package installer с страницы загрузки Go.
  • Установите.
  • Проверьте:
  • Способ 2 (через Homebrew, если он у вас уже есть):

    Linux

    Обычно проще всего поставить Go из официального архива (так вы получаете актуальную версию):

  • Скачайте архив для Linux с страницы загрузки Go.
  • Распакуйте в /usr/local (типичный вариант из официальной инструкции).
  • Добавьте Go в PATH (если установка сама этого не сделала).
  • Проверьте:
  • Если на любом этапе что-то не совпадает, сверяйтесь с официальной инструкцией установки.

    Проверка окружения: go version и go env

    После установки полезно убедиться, что:

  • Go действительно запускается
  • Переменные окружения и пути настроены корректно
  • Команды:

    go env выводит настройки Go. Новичку важно понимать только идею:

  • PATH — список папок, где система ищет исполняемые файлы (чтобы команда go находилась из любого места)
  • GOMOD — путь к файлу go.mod текущего проекта (или пусто, если вы не в проекте)
  • > Если команда go не находится, проблема почти всегда в PATH. В этом случае проще всего вернуться к официальной инструкции установки и повторить шаг с настройкой PATH.

    Папка проекта и Go modules (современный стандарт)

    Раньше в Go часто говорили про GOPATH как обязательную папку для проектов. Сейчас стандарт — Go modules, и проекты можно хранить где угодно.

    Ключевая идея:

  • Каждый проект — это отдельная папка
  • В папке лежит файл go.mod, который описывает модуль (имя проекта) и зависимости
  • Вы будете создавать go.mod командой go mod init.

    Инструменты разработчика: терминал, gofmt, go build, go run

    У Go есть набор встроенных инструментов, и вы начнёте пользоваться ими с первого же дня.

  • go run — быстро запустить программу без явной сборки исполняемого файла
  • go build — собрать программу (создать исполняемый файл)
  • gofmt — форматировать код по стандарту Go
  • Важно: стиль кода в Go обычно не обсуждают — его приводит к стандарту gofmt.

    Выбор редактора кода

    Подойдут разные варианты, но самый доступный для новичка — Visual Studio Code.

  • Установите Visual Studio Code.
  • Поставьте расширение для Go: Go (расширение для VS Code).
  • Что обычно даёт расширение:

  • Подсветка синтаксиса
  • Автодополнение
  • Подсказки по ошибкам
  • Удобный запуск и отладка
  • Форматирование через gofmt
  • Альтернатива: JetBrains GoLand (платная IDE), но для старта VS Code более чем достаточно.

    Первая программа: Hello, world

    Сделаем минимальный проект.

    Шаг 1. Создайте папку проекта

    Создайте папку, например go-first.

    Шаг 2. Инициализируйте модуль

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

    Это создаст файл go.mod. Строка example.com/go-first — имя модуля. Для учебных проектов можно использовать такое имя (оно не требует реальной регистрации домена).

    Шаг 3. Создайте файл main.go

    Создайте файл main.go со следующим содержимым:

    Разберём, что здесь происходит:

  • package main — специальный пакет для исполняемой программы
  • import "fmt" — подключение стандартной библиотеки для ввода/вывода
  • func main() — точка входа: отсюда начинается выполнение программы
  • fmt.Println(...) — печатает строку в терминал и переводит строку
  • Шаг 4. Запустите программу

    В папке проекта выполните:

    Вы должны увидеть:

    Почему go run ., а не go run main.go?

  • go run . запускает пакет из текущей папки и сам понимает, какие файлы входят в программу
  • так удобнее, когда файлов станет больше
  • !Как работает go run: временная компиляция и запуск

    Шаг 5. Соберите программу (опционально, но полезно)

    После этого рядом появится исполняемый файл:

  • на Windows: go-first.exe (или имя папки)
  • на macOS/Linux: go-first
  • Запустите его из терминала.

    Частые проблемы и быстрые проверки

    Команда go не найдена

    Признак:

  • терминал пишет что-то вроде go: command not found или "go не является внутренней или внешней командой"
  • Причина:

  • Go не добавлен в PATH или терминал был открыт до установки
  • Решение:

  • закройте и заново откройте терминал
  • проверьте шаги из официальной инструкции установки
  • Программа не запускается из-за структуры проекта

    Быстрая проверка:

  • Вы действительно находитесь в папке проекта?
  • Есть ли там go.mod?
  • Есть ли файл main.go с package main и функцией main?
  • Что дальше

    Вы установили Go, настроили инструменты и запустили первую программу. В следующей статье вы начнёте разбирать основы синтаксиса Go: переменные, типы и простые операции, чтобы писать программы осознанно, а не только повторять шаблон.

    2. Синтаксис и базовые типы: переменные, операторы, ввод-вывод

    Синтаксис и базовые типы: переменные, операторы, ввод-вывод

    В предыдущей статье вы установили Go, настроили редактор и запустили Hello, world через go run .. Теперь разберём базовый синтаксис Go: как хранить данные в переменных, какие бывают типы, как выполнять операции и как делать простой ввод-вывод.

    Как читать код на Go

    Go старается быть простым и предсказуемым:

  • Программа состоит из пакетов (файлы начинаются со строки package ...).
  • Для запуска программы используется пакет main и функция main().
  • Блоки кода задаются фигурными скобками {}.
  • Точки с запятой обычно не пишутся: Go расставляет их автоматически.
  • Минимальная структура уже знакома:

    Переменные

    Переменная — это имя, связанное со значением. В Go у переменной есть тип, и он определяет, какие значения можно хранить и какие операции допустимы.

    Объявление через var

    Если значение не задано, переменная получает нулевое значение (значение по умолчанию):

  • int0
  • float640
  • boolfalse
  • string"" (пустая строка)
  • Короткое объявление :=

    Внутри функций чаще всего используют короткую форму:

    Go сам выводит тип по правой части.

    Ограничения:

  • := работает только внутри функций.
  • Это именно объявление: слева должно появиться хотя бы одно новое имя.
  • Пример корректного использования:

    Несколько переменных

    Константы

    Константа — значение, которое нельзя изменить.

    Константы полезны для фиксированных чисел, строк, настроек.

    Базовые типы данных

    Ниже — самые частые типы, которых достаточно для первых программ.

    | Тип | Что хранит | Пример | |---|---|---| | int | целые числа (размер зависит от платформы) | -3, 0, 42 | | float64 | числа с дробной частью | 3.14, -0.5 | | bool | логические значения | true, false | | string | строки (текст) | "Go" | | byte | байт (число 0..255), часто для данных/ASCII | var b byte = 65 | | rune | символ Unicode (это int32) | var r rune = 'Ж' |

    Про byte и rune важно понимать идею:

  • string хранит текст.
  • Иногда удобнее работать с отдельными байтами (byte) или символами Unicode (rune).
  • Операторы

    Арифметика

    Для int и float64 доступны базовые операции:

  • + сложение
  • - вычитание
  • * умножение
  • / деление
  • Для целых чисел есть ещё остаток от деления:

  • % остаток (только для целых типов)
  • Пример:

    Важно: если делите два int, результат тоже int (дробная часть отбрасывается). Если нужно дробное деление, используйте float64:

    Сравнения

    Операторы сравнения возвращают bool:

  • == равно
  • != не равно
  • <, <=, >, >=
  • Логические операторы

    Работают с bool:

  • && логическое И
  • || логическое ИЛИ
  • ! логическое НЕ
  • Присваивание и инкремент

  • = присваивание
  • +=, -=, *=, /= сокращённые формы
  • Инкремент и декремент:

    Особенность Go: count++ и count-- — это отдельные инструкции, их нельзя использовать внутри выражений. То есть так нельзя:

    Ввод-вывод: печать в терминал

    Для вывода чаще всего используют пакет fmt из стандартной библиотеки: Пакет fmt.

    Основные функции:

  • fmt.Print печатает без перевода строки
  • fmt.Println печатает и добавляет перевод строки
  • fmt.Printf печатает по шаблону
  • Примеры:

    В Printf используются форматные спецификаторы (самые популярные):

  • %s строка
  • %d целое число
  • %f число с дробной частью
  • %t логическое значение
  • Ввод с клавиатуры: два подхода

    !Схема потока данных: ввод из терминала в программу и вывод обратно

    Подход A: fmt.Scan (быстро для простых случаев)

    fmt.Scan и fmt.Scanln читают значения из стандартного ввода и раскладывают их по переменным.

    Что важно понять:

  • Перед переменной стоит & — это означает передать адрес переменной, чтобы Scan мог записать туда результат.
  • Scan удобно, когда ввод разделён пробелами (например: Alice 25).
  • Ограничение: Scan не очень удобен для строк с пробелами (например, полное имя).

    Подход B: bufio.Reader + чтение строки (чаще в реальных задачах)

    Если нужно прочитать целую строку, обычно используют bufio.Reader: Пакет bufio.

    Что здесь происходит:

  • os.Stdin — стандартный ввод.
  • ReadString('\n') читает до символа перевода строки.
  • strings.TrimSpace убирает \n и лишние пробелы по краям.
  • Преобразование строки в число

    Если вы читаете строку и хотите получить число, используйте strconv: Пакет strconv.

    Здесь появляется важная для Go идея:

  • Многие функции возвращают значение и ошибку.
  • Если err не равен nil, значит произошла ошибка, и её нужно обработать.
  • Сборка мини-программы: ввод, вычисление, вывод

    Ниже пример программы, которая читает два числа и выводит сумму:

    Попробуйте запустить:

    Что дальше

    Вы научились объявлять переменные и константы, познакомились с базовыми типами, операторами и простым вводом-выводом. Следующий шаг — научиться управлять выполнением программы: условия (if) и циклы (for), чтобы писать программы, которые принимают решения и повторяют действия.

    3. Управление потоком: условия, циклы, ошибки и panic/recover

    Flow control: conditions, loops, errors, and panic/recover

    You already know how to declare variables, use basic types, do arithmetic, and print or read input. Now you’ll learn how to control what your program does next:

  • Make decisions with if and switch
  • Repeat actions with for
  • Handle failures with error values
  • Understand what panic is and how defer + recover can prevent a crash in special cases
  • These tools are enough to write small but real console programs.

    Conditions with if

    In Go, an if checks a boolean expression (true or false) and executes a block.

    A common beginner pattern: check an error

    Many functions return (value, error). If something goes wrong, error is not nil.

    if with a short statement

    Go allows you to declare a variable inside the if. The variable exists only in that if and its else.

    This style is very common in Go.

    Learn more: A Tour of Go: If

    Multi-branch logic with switch

    switch is used when you have multiple cases. In Go it’s powerful and safe.

    Key points:

  • Go switch breaks automatically after a matching case.
  • default runs if no case matches.
  • switch without an expression

    This pattern replaces long if/else if/else chains.

    If you need to force “continue into the next case”, use fallthrough, but beginners should avoid it until they really need it.

    Learn more: A Tour of Go: Switch

    Loops with for

    Go has only one loop keyword: for.

    Classic for loop

    for as “while”

    Infinite loop

    break and continue

  • break stops the loop completely.
  • continue skips to the next iteration.
  • Looping over a collection with range

    You’ll learn arrays, slices, and maps later, but range is worth seeing early.

    If you don’t need index, use _.

    Learn more: A Tour of Go: For

    Errors: the Go way

    Go does not use exceptions for normal errors. Instead, functions usually return an error value.

  • nil means no error
  • non-nil means something went wrong
  • The most important rule:

  • Always check err before using the returned value.
  • !A typical Go control flow: call a function, check err, then continue or stop.

    Creating and wrapping errors

    Use fmt.Errorf to create an error.

    You can also wrap another error (so you keep the original reason) using %w.

    Learn more: Package fmt: Errorf

    A small example: safe division

    This example shows how conditions, early returns, and errors work together.

    Learn more: Package errors: New

    panic, defer, and recover

    What is panic?

    panic stops normal execution immediately and starts unwinding the call stack. If nothing stops it, the program crashes and prints a stack trace.

    panic is not for “normal” errors like invalid user input. For those, return error.

    Typical cases where panic happens:

  • A programmer bug (like indexing out of range)
  • A situation where the program cannot continue safely
  • Learn more: Go blog: Defer, Panic, and Recover

    defer

    defer schedules a function call to run when the current function returns, even if it returns early.

    defer is commonly used to close resources:

    recover

    recover() can stop a panic only if it is called inside a deferred function.

    Important notes:

  • If you call recover() outside of defer, it won’t catch the panic.
  • If you recover, you should still decide what the program should do next (log, return an error, exit gracefully).
  • Putting it together: a small console program

    This example reads an integer, checks errors, and uses a loop to keep asking until the input is valid.

    What this program demonstrates:

  • for loop for repeated attempts
  • continue to retry on invalid input
  • break to stop when input is accepted
  • if err != nil for error handling
  • switch to categorize a value
  • What’s next

    Now you can write programs that make decisions, repeat actions, and handle failures in a Go-friendly way. Next, you’ll typically learn how to organize data (arrays, slices, maps) and how to split programs into functions so they stay readable as they grow.

    4. Функции и структуры данных: срезы, карты, строки

    Functions and data structures: slices, maps, strings

    In the previous articles you learned how to run a Go program, use variables and basic types, and control execution with if, switch, and for, including the Go-style error checks. Now you will learn the next set of tools that let you write bigger programs without chaos:

  • Functions to split logic into reusable pieces
  • Slices to store a flexible list of values
  • Maps to store key-value data for fast lookup
  • Strings to work with text correctly (including Unicode)
  • The goal of this article is practical: after it, you should be able to write a small console program that reads input, processes it using functions, stores data in slices or maps, and prints results.

    Functions

    A function is a named block of code that can:

  • take inputs (parameters)
  • produce outputs (return values)
  • be reused from different places
  • Declaring and calling a function

    Key parts:

  • func add(...) int means the function returns an int
  • return sends a value back to the caller
  • Multiple return values

    Go functions can return more than one value. This is used everywhere in the standard library.

    Calling it:

    Returning a value and an error

    This pattern connects directly to what you learned about if err != nil.

    Rules to remember:

  • nil error means success
  • on error you typically return a zero value (like 0) plus a non-nil error
  • Parameters are passed by value

    In Go, arguments are passed by value. That means the function receives its own copy of the value.

    For example, changing an int inside a function does not change the original:

    If you need to change the caller’s variable, you pass a pointer.

    Here:

  • &n means address of n
  • x means the value stored at that address*
  • Variadic functions

    A variadic function accepts “any number of arguments” of the same type.

    nums is a slice inside the function.

    Slices

    A slice is the most common way to work with lists in Go. It is a flexible view over an underlying array.

    You will see slices everywhere: in input processing, file reading, networking, and APIs.

    Creating slices

    Ways to create slices:

  • Slice literal
  • make
  • Slicing an existing slice/array
  • The key built-ins:

  • len(s) is the number of elements currently in the slice
  • cap(s) is how many elements can fit before Go needs a bigger underlying array
  • !How a slice points to an underlying array (pointer, length, capacity)

    Indexing and slicing

    Important:

  • Indexes start at 0
  • s[a:b] includes a but excludes b
  • Adding elements with append

    append may allocate a new underlying array if the old one has no capacity left.

    Slices share underlying data

    This is a common beginner trap. If you slice an existing slice, both can reference the same underlying array.

    If you need an independent copy, use copy.

    Nil slice

    A slice can be nil:

    A nil slice behaves like an empty slice in many cases (for example, you can append to it), but it is not the same as make([]int, 0).

    Maps

    A map stores values by key. Use it when you need fast lookup like “find user by name”, “count words”, “remember if we already saw something”.

    Creating and writing to a map

    You can also use a literal:

    Reading from a map and the ok idiom

    If a key is missing, you get the zero value of the value type.

    So you often need to know whether the key existed. Use the comma ok form:

    Deleting a key

    Iterating over a map

    Important:

  • map iteration order is not guaranteed
  • Nil map

    A map can be nil:

    Reading from a nil map is safe (returns zero values), but writing to a nil map causes a panic. Create it with make before writing.

    !Map lookup and the "comma ok" result

    Strings

    A string in Go is a sequence of bytes. Strings are immutable, meaning you cannot change a character “in place”.

    Basic operations

    len(s) returns the number of bytes, not the number of human-visible characters.

    Bytes, runes, and Unicode

    You already saw byte and rune earlier. Here is the practical rule:

  • byte is one raw byte (good for ASCII or binary data)
  • rune is one Unicode code point (used for characters)
  • Example with a non-ASCII character:

    To iterate over characters correctly, use range on a string. range decodes UTF-8 and yields runes.

    Here:

  • i is the byte index
  • r is the rune (Unicode code point)
  • !Why len(string) counts bytes and range iterates runes

    Converting between string and []byte / []rune

    Use cases:

  • []byte for file/network data and performance-oriented processing
  • []rune when you must manipulate characters
  • Building strings efficiently

    Because strings are immutable, repeated + concatenation in a loop can be slow.

    Use strings.Builder:

    Reference: Package strings

    Putting it together: word counter (functions + slices + maps + strings)

    This example reads one line, splits it into words, and counts occurrences.

    What this demonstrates:

  • splitting logic into functions
  • using a slice ([]string) to hold words
  • using a map (map[string]int) to count
  • using string helpers from strings
  • What’s next

    Now you can organize code into functions and store data in slices and maps while handling text properly. In the next step, you typically learn how to define your own types using struct, attach behavior with methods, and organize code into multiple files and packages.

    5. Структуры, методы и интерфейсы: основы ООП в Go

    Structs, methods, and interfaces: OOP basics in Go

    In the previous articles you learned how to write small console programs using variables, control flow, functions, slices, maps, and strings. The next step is to model your own data types and organize behavior around them.

    Go does not have “classes” in the traditional sense, but you can still build clean, maintainable, “object-like” code using:

  • Structs to group data
  • Methods to attach behavior to a type
  • Interfaces to describe behavior and write flexible code
  • This is the foundation for most real Go programs.

    Structs

    A struct is a type that groups multiple fields into a single value.

    Defining and creating a struct

    Important ideas:

  • type User struct { ... } declares a new type.
  • var u1 User creates a value with zero values in its fields.
  • User{Name: "Alice"} is a composite literal.
  • Pointers to structs

    When you need to share a single object-like value across functions, you often use a pointer.

    Go lets you write p.Name instead of (*p).Name. This is a convenience feature.

    !How a pointer references a struct value

    Methods

    A method is a function with a receiver. The receiver ties the function to a specific type.

    Defining a method

    Here, (u User) is the receiver.

    Value receiver vs pointer receiver

    A key design choice is whether the receiver is a value (User) or a pointer (*User).

  • Use a value receiver when the method does not need to modify the receiver and the type is small and cheap to copy.
  • Use a pointer receiver when the method must modify the receiver or copying would be expensive.
  • Example: a method that changes the user.

    What happens conceptually:

  • Birthday needs to modify Age.
  • A pointer receiver allows the method to update the original value.
  • Reference: A Tour of Go: Methods

    Methods help you keep logic close to data

    Compare these two styles:

  • Free function: isAdult(u User) bool
  • Method: u.IsAdult() bool
  • Both work, but methods often make code easier to read because behavior is grouped with the data type.

    Composition and embedding

    Go encourages composition instead of deep inheritance hierarchies.

    Embedding a struct

    Embedding means placing a type inside another struct without a field name.

    Embedding gives you:

  • Field and method promotion (you can access embedded fields/methods directly)
  • A simple way to build types from smaller parts
  • This is a common Go alternative to inheritance.

    Interfaces

    An interface describes behavior: a set of method signatures.

    A small interface example

    Key Go idea:

  • You do not “declare” that a type implements an interface.
  • If a type has the required methods, it implements the interface automatically.
  • This is called implicit interface satisfaction.

    Reference: A Tour of Go: Interfaces

    !Implicit implementation of an interface in Go

    Why interfaces matter

    Interfaces let you write functions that depend on behavior, not on concrete types.

    Common benefits:

  • Easier testing (swap real implementation with a fake)
  • Cleaner architecture (decouple components)
  • More reusable code
  • The most famous interface: error

    Go uses the built-in error interface:

    That is why functions can return “an error” from many different packages and implementations.

    Reference: Package errors

    Interface values and nil

    An interface value holds two things:

  • a concrete value
  • the concrete type of that value
  • That leads to a common pitfall:

  • An interface can be non-nil even if it contains a nil pointer.
  • Example:

    Practical rule for beginners:

  • If you want to return “no error”, return nil directly.
  • Be careful returning typed nil pointers as an error.
  • Reference: Effective Go: Interfaces and types

    Type assertions (getting the concrete type back)

    Sometimes you have an interface value and need to check what it really is.

  • any is an alias for interface{}.
  • x.(Person) is a type assertion.
  • ok tells you whether the assertion succeeded.
  • A practical mini-example: Stringer for nicer printing

    The standard library defines fmt.Stringer:

    If your type implements it, fmt.Println will use your String() automatically.

    Reference: Package fmt: Stringer

    Putting it together: a small registry using structs + methods + interfaces

    This example models a tiny “user registry” that stores users in a map, adds behavior with methods, and returns errors.

    What to notice:

  • Registry is a struct with a map inside (you learned maps earlier).
  • NewRegistry returns a pointer so methods can work on shared state.
  • Methods Add and Get define behavior.
  • Errors are returned as error interface values.
  • What’s next

    Now you can model real-world entities with structs, attach logic with methods, and decouple code with interfaces. The next natural steps are:

  • Organizing code into multiple files and packages
  • Working with files and JSON
  • Building a small CLI or HTTP service using these building blocks
  • 6. Работа с пакетами, модулями, файлами и HTTP-основы

    Working with packages, modules, files, and HTTP basics

    You already know how to write programs with variables, control flow, functions, slices, maps, and structs/interfaces. Now you’ll learn how Go code is organized into packages and modules, how to read/write files, and how to do basic HTTP client/server work.

    These topics are the bridge from “single-file console programs” to “small real projects”.

    Packages and modules: what they are and why you need both

    Package

    A package is a unit of code organization inside your project.

  • A folder usually corresponds to one package.
  • All .go files in the same folder normally share the same package <name>.
  • You import packages to reuse code.
  • Official reference: Effective Go: Packages

    Module

    A module is a versioned collection of packages.

  • A module is defined by a go.mod file.
  • The module has a module path (like example.com/myapp).
  • Imports inside and outside your module use import paths.
  • Official reference: Go Modules Reference

    A mental model

  • Module answers: “What project is this, and what are its dependencies?”
  • Package answers: “How is this project split into reusable parts?”
  • !How module and packages relate in a typical Go project

    Importing packages and the standard library

    You already used standard library packages like fmt, strings, bufio. The import syntax looks like this:

    Or multiple imports:

    You can browse standard library docs on pkg.go.dev:

  • Package os
  • Package bufio
  • Package net/http
  • Exported vs unexported names (a core Go rule)

    In Go, whether something is public (exported) depends on its name:

  • Exported: starts with a capital letter, like CountWords, User, NewRegistry
  • Unexported: starts with a lowercase letter, like countWords, user, newRegistry
  • This matters when you split code into packages:

  • Code inside the same package can use both exported and unexported identifiers.
  • Code from another package can use only exported identifiers.
  • Creating a multi-package project

    Let’s build a tiny project that reuses your earlier skills (functions, maps, strings), but organized properly.

    Suggested folder structure

  • main.go is your executable program (package main).
  • textutil is a reusable package with functions.
  • Step 1: initialize a module

    From the project root:

    Step 2: create a reusable package

    Create textutil/textutil.go:

    Notice the capital letters:

  • WordsFromLine and CountWords are exported, so main can use them.
  • Step 3: use the package from main

    Create main.go:

    Run it:

    Working with files

    Most real programs need to read or write files: configs, logs, exported results, user input.

    Reading a whole file (simple and common)

    Use os.ReadFile when the file is not huge:

    Reference: os.ReadFile

    Writing a whole file

    Use os.WriteFile:

    Reference: os.WriteFile

    The permission number 0644 is a typical default on Unix-like systems:

  • owner can read/write
  • others can read
  • On Windows, permissions behave differently, but os.WriteFile still works.

    Streaming: reading line by line

    If files can be large, read them gradually. A simple approach is bufio.Scanner:

    Reference: bufio.Scanner

    Mini-example: word count from a file path

    This ties together modules/packages, file reading, and functions returning maps.

    Create main.go like this (keeping textutil from earlier):

    Run:

    HTTP basics: client and server

    HTTP is the foundation of web APIs. In Go, the standard library net/http is powerful and widely used.

    Reference: Package net/http

    How HTTP works (very briefly)

  • The client sends a request (method like GET, URL, headers, optional body).
  • The server returns a response (status code like 200, headers, body).
  • !HTTP request/response flow

    HTTP client: making a GET request

    A minimal example:

    Key points:

  • Always defer resp.Body.Close() to free resources.
  • Check resp.StatusCode before trusting the body.
  • io.ReadAll reads the response body fully.
  • Reference: io.ReadAll

    A safer client with a timeout

    For real programs, set a timeout:

    Without a timeout, your program can hang for a long time on network problems.

    HTTP server: your first endpoint

    A Go HTTP server is built from handlers. A handler is a function that receives:

  • http.ResponseWriter to write the response
  • *http.Request to read the request
  • Example server with two endpoints:

    Try in a browser:

  • http://localhost:8080/health
  • http://localhost:8080/echo?msg=hello
  • Or from terminal:

    Reference: http.ListenAndServe

    How this connects to what you already learned

  • Functions: handlers are just functions.
  • Maps and strings: query parameters and text processing use the same tools.
  • Structs and interfaces: net/http heavily uses interfaces (for example, http.Handler), and you can design your own types that implement them later.
  • What to do next

    After this article, you can start building small “real” projects:

  • a CLI tool that reads files and prints results
  • a tiny HTTP service that returns computed results
  • a project organized into multiple packages inside a module
  • If you continue learning, the most useful next topics are:

  • JSON encoding/decoding with encoding/json
  • testing with go test
  • project layout conventions (what goes into cmd/, internal/, pkg/)
  • 7. Конкурентность: goroutine, channels и простые паттерны

    Конкурентность: goroutine, channels и простые паттерны

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

    В Go конкурентность встроена в язык и стандартную библиотеку. Основа этого подхода:

  • goroutine — лёгкая единица выполнения (похожа на поток, но значительно дешевле)
  • channel — канал для передачи значений между goroutine
  • select — ожидание сразу нескольких операций с каналами
  • > В Go часто придерживаются идеи: «Не делитесь памятью для общения; общайтесь, делясь памятью». Это цитата из доклада Rob Pike, одного из авторов Go: Go Concurrency Patterns (Rob Pike)

    Конкурентность и параллелизм

    Эти слова часто путают:

  • Конкурентность — программа умеет переключаться между задачами и продвигать их вперёд независимо друг от друга.
  • Параллелизм — задачи реально выполняются в одно и то же время на разных ядрах CPU.
  • Go даёт конкурентность как модель программирования. Параллелизм обычно появляется автоматически, если есть несколько ядер.

    Goroutine

    Запуск goroutine

    Чтобы запустить функцию конкурентно, используйте ключевое слово go:

    Что здесь важно:

  • go work("A") запускает work в отдельной goroutine.
  • work("B") выполняется в текущей goroutine (в данном случае это главная).
  • Частая ошибка новичка: программа завершается слишком рано

    main() завершился — программа завершилась, даже если другие goroutine ещё работали.

    Плохой способ “подождать” — time.Sleep в main: это ненадёжно.

    Правильный базовый инструмент ожидания — sync.WaitGroup.

    Ожидание goroutine через sync.WaitGroup

    WaitGroup — счётчик: сколько goroutine мы ждём.

    Правило для новичка:

  • wg.Add(n) делайте до запуска goroutine
  • defer wg.Done() внутри каждой goroutine
  • wg.Wait() там, где нужно дождаться завершения
  • Документация: sync.WaitGroup

    Channels

    Channel — это типизированная “труба” для передачи значений между goroutine.

    Создание канала

  • chan int означает “канал целых чисел”.
  • make создаёт канал.
  • Отправка и получение

  • Отправка: ch <- 10
  • Получение: v := <-ch
  • Пример: одна goroutine считает сумму, другая получает результат.

    Небуферизованный канал

    Канал без размера буфера (как make(chan int)) называется небуферизованным.

    Его ключевое свойство:

  • отправка ch <- x блокируется, пока другая goroutine не сделает получение <-ch
  • получение <-ch блокируется, пока кто-то не отправит значение
  • Это удобно для синхронизации.

    Буферизованный канал

    Буферизованный канал создаётся так:

    Это означает:

  • можно отправить до 3 значений без немедленного ожидания получателя
  • отправка блокируется, когда буфер заполнен
  • получение блокируется, когда буфер пуст
  • Буфер полезен, когда производитель и потребитель работают с разной скоростью.

    !Схема различий буферизованного канала: заполнение буфера и блокировки

    Закрытие канала: close

    close(ch) означает: “значений больше не будет”.

    Важные правила:

  • Закрывает канал тот, кто отправляет значения, а не тот, кто читает.
  • Закрытие нужно не всегда. Оно нужно, когда получателю важно понять, что поток данных завершён.
  • Как читать из канала до закрытия:

    Цикл range завершится, когда канал будет закрыт и все значения будут прочитаны.

    Также есть “comma ok” при чтении:

    select: ожидание нескольких каналов

    select позволяет ждать несколько возможных операций с каналами.

    Пример: таймаут

    Что здесь происходит:

  • time.After(d) возвращает канал, который “сработает” через d
  • select выполнит первый готовый case
  • Документация: time.After

    default в select

    Если добавить default, то select не будет ждать — выполнит default, если ни один case не готов.

    Это полезно для неблокирующей попытки чтения/записи, но новичкам важно помнить: default легко превращает цикл в “пустое кручение” CPU.

    Простые конкурентные паттерны

    Паттерны — это повторяемые схемы решения задач. Ниже — самые полезные для старта.

    Паттерн: pipeline (конвейер)

    Идея: данные проходят через несколько стадий обработки.

    Здесь появляются полезные детали:

  • in <-chan int означает “канал только для чтения”
  • out chan<- int (не показано) означало бы “канал только для записи”
  • Это делает код безопаснее: функция не сможет случайно использовать канал не по назначению.

    Дополнительно (углубление): Go blog: Pipelines and cancellation

    Паттерн: worker pool (пул работников)

    Идея: есть очередь задач, и несколько goroutine-работников берут задачи и выполняют их параллельно.

    Ключевые идеи:

  • jobs закрывает отправитель задач
  • results закрывает тот, кто точно знает, что отправок больше не будет (здесь это отдельная goroutine после wg.Wait())
  • !Схема пула работников: задачи, несколько обработчиков и сбор результатов

    Паттерн: fan-out и fan-in

  • fan-out: одна горутина генерирует задачи, много горутин обрабатывают.
  • fan-in: много источников отправляют результаты в один канал (или один потребитель).
  • Worker pool выше — это fan-out + сбор результатов (упрощённый fan-in).

    Типичные проблемы и как их избегать

    Deadlock (взаимная блокировка)

    Deadlock — ситуация, когда все goroutine “ждут друг друга”, и программа не может продолжать.

    Частые причины:

  • отправка в канал без получателя
  • получение из канала, в который никто не отправит
  • забыли закрыть канал, а получатель ждёт range до конца
  • Подсказка: сообщения fatal error: all goroutines are asleep - deadlock! означают, что программа застряла.

    Утечки goroutine

    Утечка — когда goroutine осталась висеть навсегда (обычно в ожидании чтения/записи в канал), а программа продолжает жить и накапливает такие goroutine.

    Практическое правило:

  • если вы запускаете goroutine, у неё должен быть понятный способ завершиться
  • Для больших программ часто используют context.Context, но на старте достаточно:

  • закрытия канала задач
  • отдельного “сигнального” канала завершения
  • Документация: context

    Data race (гонка данных)

    Гонка данных возникает, когда две goroutine одновременно читают и пишут одну и ту же переменную без синхронизации.

    Базовые способы избежать:

  • передавать данные через каналы (вместо общего доступа)
  • или защищать общие данные мьютексом (sync.Mutex) — это отдельная важная тема, но знать о ней полезно
  • Практика: запускайте программы с детектором гонок:

    Документация: The Go Race Detector

    Как это связано с предыдущими темами курса

  • Функции: почти всё конкурентное в Go строится на функциях (goroutine запускает функцию, handler в HTTP — функция).
  • Структуры: удобно передавать задачи и результаты как struct (пример Job и Result).
  • Пакеты и модули: конкурентную логику можно вынести в отдельный пакет (например, пакет workerpool).
  • HTTP-основы: конкурентность особенно важна в сетевых программах (параллельные запросы, обработка нескольких клиентов).
  • Что дальше

    Вы освоили основу конкурентности Go: goroutine, channels, select и несколько практичных паттернов. Следующие шаги (когда будете готовы):

  • аккуратная отмена операций через context
  • защита общих данных через sync.Mutex
  • тестирование конкурентного кода и поиск гонок
  • Полезный справочник по теме: A Tour of Go: Concurrency