---
title: "Теги required, min, max"
description: "validator required tag и min max валидация на живой форме: не-нулевые значения, длины строк в рунах, диапазоны чисел, omitempty и указатели против нулей."
course: "Валидация в Go: go-playground/validator"
url: "https://golangmaster.ru/courses/validator/lessons/03-tegi-required-min-max/"
published: "2026-09-19"
updated: "2026-09-21"
minutes: 18
---

# Теги required, min, max

*Курс «Валидация в Go: go-playground/validator», урок 3 из 25. ~18 минут. Обновлено 21.09.2026. Источник: https://golangmaster.ru/courses/validator/lessons/03-tegi-required-min-max/*

Теги required, min и max закрывают половину правил любой формы: обязательность, длины и диапазоны. В этом уроке — живая форма профиля: где required ловит ноль вместо «заполнено», почему min для строк считает руны, а не байты, как omitempty делает поля необязательными и зачем на числах нужен указатель. Каждый тег разбираем с граблями, которые обычно находят только в проде.

Скелет формы уже готов с прошлого урока: структура, конструктор валидатора, вызов Struct. Теперь наполняем правила. Наша форма профиля — ProfileForm с четырьмя полями: Username, Password, Age и необязательный About. Для каждого поля зададим комбинацию обязательности и границ, а попутно разберём главный сюрприз этой триады: required проверяет не «заполнено ли», а «не нулевое ли значение», и именно на этом зазоре живут самые частые баги новичков. Диапазоны тоже не так просты, как выглядят: min и max меняют смысл в зависимости от типа поля, и это надо выучить один раз — на всю карьеру с валидатором. План урока такой: сначала required и его зазор между «нулевым» и «заполненным», затем полиморфизм min и max с таблицей по типам, дальше omitempty и указатели как два способа сказать «поле необязательное». Каждому тегу — своя грабля из практики, и к концу урока вы соберёте чек-лист правил, которым будете пользоваться во всех следующих формах курса.

### validator required tag: не-нулевое, а не «заполненное»

required звучит как «поле заполнено», но реализовано строго: поле не должно быть нулевым значением своего типа. Для строк это пустая строка — и только: строка из пробелов required пройдёт, потому что она не пустая. Для чисел — ноль, для bool — false, для указателей и интерфейсов — nil, для срезов и карт — nil или нулевая длина. Отсюда первая крупная ловушка курса: числовое поле, где ноль — легитимное значение. Количество детей, этаж, скидка в процентах — во всех этих случаях required отвергнет честный ноль, и пользователь формы получит «обязательное поле», хотя ничего не забыл. То же самое с деньгами: сумма перевода 0 на int с required уедет в отказ, хотя пользователь мог ввести ноль осознанно — перевод между своими счетами без комиссии. Различать «не указано» и «указан ноль» можно только указателем: *int с required,gte=0 пропустит честный ноль и отвергнет отсутствующее значение. Решений два: пересмотреть, действительно ли ноль допустим (если нет — нужен диапазон вроде gte=1), либо перейти на указатель, где nil и ноль различимы. И вторая ловушка — строка из пробелов: required её пропустит, а бизнес примет «имя пользователя» из одних пробелов. Отсюда правило: чистка пробелов — это нормализация перед валидацией, у неё отдельный урок курса. Третье следствие из семантики required — пустые контейнеры: срез тегов длиной ноль required отвергнет так же, как ноль числа; если пустой список легитимен, поле описывается парой omitempty,min=1 — «пусто можно, непусто хотя бы один элемент».

### Форма профиля: четыре профиля правил

```go
type ProfileForm struct {
        Username string   `validate:"required,min=3,max=32"`
        Password string   `validate:"required,min=8,max=72"`
        Age      *int     `validate:"required,gte=14"`
        About    string   `validate:"omitempty,max=500"`
}

// Три поля обязательны, About — нет.
if err := validate.Struct(&form); err != nil {
        // ValidationErrors: по записи на каждое сломанное поле
}
```

Четыре поля — четыре рабочих комбинации. Username и Password обязательны с диапазоном длины; Age — указатель с required: nil означает «не указан», а не нулевой возраст, и диапазон gte проверяется по значению после разыменования; About необязателен, но непустой — не длиннее пятисот символов. Каждую комбинацию разберём по отдельности ниже.

### min max валидация: три смысла одного тега

min и max — самые полиморфные теги валидатора: их смысл зависит от типа поля. Для строк они считают длину, причём в рунах, а не в байтах: «привет» — семь символов, хотя байтов двенадцать; для кириллических форм это принципиально, потому что len() по байтам дал бы вдвое больше. Для чисел min и max сравнивают само значение: min=1 на int отвергает ноль и отрицательные числа. Для срезов, карт и массивов — количество элементов: min=1 на []string требует хотя бы один элемент. Границы у min/max включительные: значение ровно на границе проходит проверку. Если нужна строгая граница, берите gt и lt, а нестрогие аналоги gte и lte для чисел работают как «минимально/максимально допустимое». Точный размер даёт len=13 — например, для строк фиксированной длины вроде штрихкодов и кодов подтверждения. На практике полиморфизм стреляет вот где: команда пишет max=140, думая о лимите твита, а поле — заголовок задачи в символах; для латиницы лимит совпадает с подсчётом фронтенда, для кириллицы — совпадает тоже (руны везде), а вот если фронт считает байтами — начинается спор про «пятьдесят символов» против «сотни байтов». Договаривайтесь на рунах и фиксируйте это в доках API. Таблица ниже собирает всю матрицу «тег против типа» на один экран — вернитесь к ней, когда сомневаетесь.

### Поведение тега по типам поля

| Тег | Строка | Число | Срез/карта | Указатель |
| --- | --- | --- | --- | --- |
| required | не пустая | не 0 | не nil и не пустой | не nil |
| min=N | длина ≥ N рун | значение ≥ N | элементов ≥ N | правило по типу за разыменованием |
| max=N | длина ≤ N рун | значение ≤ N | элементов ≤ N | правило по типу за разыменованием |
| gt=N / lt=N | — | строго больше / меньше | — | правило по типу за разыменованием |
| len=N | ровно N рун | — | ровно N элементов | — |
| omitempty | нулевой — остальные правила пропускаются | то же | то же | nil пропускается |

### Проверка: руны или байты

**Вопрос:** Строка «Йо» и тег min=3. Пройдёт ли проверка?

- Пройдёт: в строке четыре байта, а 4 ≥ 3
- ✅ Не пройдёт: min считает руны, а их две
- Пройдёт: пустые и короткие строки min игнорирует
- Зависит от кодировки формы

min и max для строк считают символы (руны), а не байты: «Й» и «о» — две руны против четырёх байтов UTF-8. Это частая причина споров с фронтендом, который считает по-своему, — сверяйте правило с подсчётом символов, а не байтов, иначе кириллические формы будут валиться в 400 без видимой причины.

### omitempty: компаньон опциональных полей

Необязательное поле — не то же самое, что поле без правил. Поставили min=3 на необязательное поле — и пустая строка провалится: min проверяет длину, а нулевая длина меньше трёх. omitempty решает конфликт: если поле нулевое значение своего типа, все остальные правила тега пропускаются. Комбинация omitempty,min=3,max=500 читается как договор: пусто — можно; непусто — от трёх до пятисот символов. Порядок правил в теге роли не играет — omitempty может стоять и в конце, — но конвенция ставить его первым улучшает читаемость. Частая ошибка новичка противоположна: привычка вешать omitempty на всё подряд, включая обязательные поля. required и omitempty в одном теге противоречат друг другу: required отвергает нулевое, которое omitempty только что пропустил, — поведение получается запутанным и зависит от порядка исполнения правил. Выбирайте одно из двух: поле обязательное или нет. Отдельный кейс для omitempty — PATCH-обновления: клиент присылает только изменённые поля, остальное отсутствует, и после декодирования структура заполнена нулями. Без omitempty валидация всей структуры отвергла бы такие запросы пачкой; с omitempty на необязательных полях и точечным перечислением обязательных через StructPartial — метод из прошлого урока — частичные обновления описываются без отдельной структуры на каждый эндпоинт. И ещё разница, о которой забывают: поле, отсутствующее в JSON, и поле с пустой строкой после декодирования для валидатора неразличимы — оба нулевые. Поэтому omitempty честно описывает оба случая сразу, а вот проверка «пользователь прислал, но пустое» тегами не решается — нужны указатели или приёмы из урока про DTO-границы.

> **min вместо required: бессмысленное сообщение.** Типичная ошибка: Age int с тегом min=13 вместо required,gte=13. Клиент не прислал поле вовсе — после декодирования там ноль — и валидатор отвечает тегом min, хотя пользователь ничего не указывал. Сообщение выходит бессмысленным («слишком мало» для поля, которого нет), а фронтенд подсвечивает инпут, который пользователь не трогал. Обязательность и диапазон — разные правила: required говорит «укажите», min, max, gte и lte — «укажите в этих пределах».

### Указатели: nil, ноль и «не указано»

```go
// nil и «не указано» — разные вещи, и указатель это различает.
type MeetingForm struct {
        Attendees *int `validate:"required,gte=2,lte=50"`
}

// {"attendees":null}  → required: nil-указатель, значения нет
// {"attendees":0}     → gte=2: число указано, но 2..50 не попало
// {"attendees":12}    → прошло: 12 в диапазоне
```

На указателе required означает «не nil»: поле обязали указать, а ноль внутри — конкретное значение, которое проверит gte. Без указателя int не различил бы «не прислали» и «прислали ноль» — после декодирования оба выглядят нулём. Бонус связки: после успешной проверки Attendees гарантированно не nil, и разыменование в бизнес-коде безопасно.

### Точные границы: len, eq, ne, gt и lt

Между «от» и «до» живёт семья точных проверок. len=N требует точного размера: строка из тринадцати символов для штрихкода, срез ровно из шести цифр кода подтверждения. eq и ne сравнивают значение с константой: eq=0 удобен для «поле должно быть нулём» в статусных структурах, ne — для запрета конкретных значений, хотя чаще список запретов переворачивают в oneof с перечнем допустимых. Для чисел полезна пара gt и lt — строгие границы: gt=0 исключает ноль там, где min=0 его пропускает, и это спасает от очередного бага «минимальное количество — ноль». Все эти теги подчиняются той же логике типов: строки считают руны, числа сравнивают значения, срезы — количество элементов. И так же, как min и max, они не прощают нулевых значений без omitempty: точная проверка на необязательном поле требует пары с omitempty, иначе пустая строка провалит len=6. Границы честнее проверять на граничных значениях: min=3 пропускает ровно три символа и ровно три элемента — тест со строкой из трёх символов скажет о правиле больше, чем чтение документации. Если поведение тега кажется неочевидным, соберите его локально на пяти значениях: ниже границы, на границе, выше, нулевое и пустое — эта пятёрка вскрывает любой полиморфный тег быстрее, чем чтение чужих туториалов.

### Что вернёт проверка формы

```go
type TicketForm struct {
        Promo string `validate:"omitempty,min=4,max=16"`
        Seats int    `validate:"min=1,max=6"`
}

in := TicketForm{Promo: "", Seats: 1}
err := validate.Struct(&in)
// что в err?
```

**Что выведет программа?**

- ✅ nil — Promo пустой пропущен omitempty, Seats в диапазоне
- Ошибка по Promo: min=4 не пропускает пустую строку
- Ошибка по Seats: min=1 требует больше одного
- Ошибки по обоим полям сразу

omitempty пропускает правила min/max на пустой строке — Promo валиден. Seats = 1 попадает на включительную границу min=1 и проходит. Оба поля чисты — err равен nil. Уберите omitempty из тега Promo — появится ошибка min: пустая строка короче четырёх символов.

### Один тег — цепочка правил через запятую

Внутри тега правила разделяются запятыми и исполняются слева направо до первого провала: validate:"required,min=3,max=32" — сначала required, потом min, потом max; споткнувшись, поле завершает проверку, и остальные правила не выполняются. Параметр правила идёт через знак равенства: min=3. Пробелы вокруг правил — источник тихих сюрпризов: пробел после запятой превращает min=3 в неизвестный тег с именем « min», и валидатор паникует Undefined validation function на первом же прогоне структуры — форматируйте теги без пробелов. Перечень допустимых значений задаёт oneof, где значения разделяются уже пробелами: oneof=admin editor viewer — внутри одного тега грамматика двойная, запятые между правилами, пробелы внутри перечня. Форматы вроде email и datetime разобраны в следующем уроке, а здесь напомним про нормализацию: правила работают с тем, что им дали, — trim и нижний регистр делайте до validate.Struct, иначе « a@b.ru » придётся описывать тегами на каждый случай.

### Частые грабли required и min/max

- min на строке считает руны, а не байты — не сверяйте его с len()
- required на числе отвергает честный ноль — если ноль легитимен, берите указатель или omitempty
- omitempty и required в одном теге противоречат друг другу — выберите одно
- пробел после запятой в теге — неизвестный тег и паника Undefined validation function
- min и max включительные: для строгой границы нужны gt и lt
- min на срезе требует элементы, а не символы внутри строк — для элементов есть [dive](/courses/validator/lessons/08-srezy-i-dive/)

### Проверка: required на булевом поле

**Вопрос:** Поле Confirmed bool с тегом required. Пользователь не поставил галочку. Что вернёт валидатор?

- nil: bool — не строка, required его не трогает
- ✅ Ошибку по required: false — нулевое значение типа
- Ошибку по min: false меньше единицы
- Зависит от того, пришло ли поле в JSON

false — нулевое значение bool, и required отвергает его как пустое. Отсюда практическое правило: чекбоксы согласия проверяйте required, а флаги, где false — легитимное состояние, тегом не трогайте вовсе или переходите на *bool, различающий «не указано» и «выключено».

> **Спорное: required на каждом поле.** Спорное мнение с условием: не ставьте required по умолчанию. Требуйте только то, без чего логика не работает, — остальным достаточно omitempty с диапазоном. Сценарий поломки: API добавляет обязательное поле — и мобильное приложение прошлого релиза, которое о нём не знает, падает с 400 на каждом запросе. Обязательное поле — коммит перед всеми потребителями API; поле новой фичи чаще должно быть опциональным, пока старые клиенты не обновлены.

### Соберите правила формы

Заполните пропуски: обязательность имени, опциональность описания, нижняя граница возраста и точная длина кода.

```go
type ProfileForm struct {
        Username string   `validate:"___,min=3,max=32"`
        About    string   `validate:"___,max=500"`
        Age      *int     `validate:"required,___=14"`
        Code     string   `validate:"omitempty,___=6"` // ровно 6 символов
}
```

Подсказка: Обязательное поле — required; необязательное — omitempty; нестрогая нижняя граница числа — gte; точная длина строки — len.

Правильные ответы в пропусках: `required`, `omitempty`, `gte`, `len`

### Соберите путь формы через валидатор

Клиент отправил имя из одной буквы и не указал возраст. Разложите события по порядку.

Правильный порядок строк:

1. `Клиент отправляет форму: username "И", age null`
2. `Хендлер декодирует JSON в структуру ProfileForm`
3. `validate.Struct(&form): обход полей по тегам слева направо`
4. `Username проваливает min=3: одна руна против трёх`
5. `Age проваливает required: nil-указатель без значения`
6. `ValidationErrors: две записи FieldError`
7. `Хендлер собирает сообщения: «от 3 символов», «укажите возраст»`
8. `Ответ 400 — форма подсвечивает два поля`

### Карточки: триада тегов

- **required** — Не-нулевое значение типа: не "", не 0, не false, не nil, не пустой срез. Честный ноль числа он не отличит от «не указано» — для этого нужен указатель.
- **min / max** — Для строк — длина в рунах, для чисел — значение, для срезов и карт — элементы. Границы включительные; строгие границы — gt и lt.
- **omitempty** — Нулевое поле пропускает остальные правила тега: пустое можно, непустое — по правилам. Рядом с required противоречит сам себе.
- **указатель + required** — required на *T означает «не nil»: различает «не указано» и ноль. После успешной проверки разыменование безопасно.
- **oneof** — Значение из перечня, разделённого пробелами: oneof=admin editor viewer. Запятая внутри тега — уже разделитель правил.

Следующий урок — [форматы: email, url, uuid и datetime](/courses/validator/lessons/04-email-url-format/): длины и диапазоны разобрали, переходим к проверке синтаксиса значений без ручных регулярных выражений. Ошибки, которые возвращают эти теги, разбираем в [пятом уроке](/courses/validator/lessons/05-oshibki-validacii/) — методы Field, Tag и Param превращают провал min в понятный ответ пользователю. Сообщения на русском собираются в [шестом уроке](/courses/validator/lessons/06-russkie-soobshcheniya/), а чистка пробелов перед проверками живёт в отдельном уроке про нормализацию. Код формы соберите локально: песочница сайта исполняет только чистый Go без внешних модулей, поэтому валидатор живёт в вашем терминале, а интерактивы закрепляют правила без запуска.

## Коротко: что запомнить

- required — не-нулевое значение типа: пустая строка, 0, false, nil, пустой срез; честный ноль он не отличит от «не указано».
- min и max полиморфны: руны у строк, значение у чисел, элементы у срезов; границы включительные, строгие — gt и lt.
- min для строк считает руны, а не байты: кириллица не удваивает длину, len() — удваивает.
- omitempty пропускает остальные правила на нулевом поле; в паре с required противоречит сам себе.
- Указатель на число различает «не указано» и ноль; required на *T — это «не nil».
- Правила в теге идут через запятую до первого провала; пробел после запятой — паника Undefined validation function.

## Домашнее задание

Возьмите форму из урока и добавьте два поля: Promo string с omitempty,min=4,max=16 и Seats *int с required,gte=1,lte=6. Прогоните локально четыре случая: оба заполнены валидно, Promo пуст, Seats = 0, Seats = nil. Запишите, какие теги сработали в каждом случае, и объясните себе, почему Seats = 0 и Seats = nil дали разные ошибки.

## Вопросы и ответы по уроку

### Почему required отвергает 0, хотя это легитимное значение?

required проверяет нулевое значение типа, а ноль числа — нулевое значение. Если ноль допустим по смыслу, различите «не указано» и ноль указателем *T, либо разрешите пустоту omitempty и проверяйте диапазон только при наличии значения. Оба приёма разобраны в уроке выше — на Age и About.

### min=3 считает байты или символы?

Символы (руны). «Йо» — две руны и четыре байта, min=3 её не пропустит. Правила для строк считают длину в рунах, поэтому кириллица не даёт двойную длину, как это делает len(). Если фронтенд считает лимиты по-своему, разногласия вскрываются именно на кириллице — сверяйте подсчёт и фиксируйте правило в доках API.

### Можно ли совместить required и omitempty?

Не стоит: omitempty пропускает правила на нулевом поле, а required требует ненулевое — конфликт тегов делает поведение запутанным. Выберите одно: поле обязательное (required) или необязательное (omitempty плюс правила).
