1. Зачем нужны argument labels
Когда вы впервые видите Swift‑вызов вроде distance(from: 3, to: 10), возникает ощущение: «Ого, сколько букв ради пары чисел». Но эти «буквы» — не украшение. Они помогают компилятору и человеку одновременно: компилятору — проверять корректность вызова, а человеку — читать код как фразу, не вспоминая порядок параметров по памяти (которая и так занята паролями от Wi‑Fi).
Swift изначально проектировался с идеей, что хороший код читается почти как естественный язык. Поэтому argument labels становятся частью «публичного лица» функции: они показывают смысл каждого аргумента и уменьшают шанс перепутать два одинаковых по типу значения, например два Int или два String.
Пример: без labels и с labels
Сравните два варианта — оба компилируются, но один читается как «набор чисел», а другой как «понятная операция»:
func rectangleArea(_ a: Int, _ b: Int) -> Int {
return a * b
}
print(rectangleArea(3, 7)) // 21
func rectangleArea(width: Int, height: Int) -> Int {
return width * height
}
print(rectangleArea(width: 3, height: 7)) // 21
Во втором случае вы почти физически не можете «случайно» перепутать аргументы местами, потому что компилятор заставит вас подписать каждое значение: width: и height:.
2. Внешние и внутренние имена параметров
Сейчас подведём важный фундамент. В Swift у параметра функции часто есть внешнее имя (argument label) — то, что пишется при вызове, и внутреннее имя — то, чем вы пользуетесь внутри тела функции. Иногда они совпадают, иногда — разные. Смысл в том, чтобы одновременно добиться читаемого вызова и удобного кода внутри функции.
Давайте разложим это в маленькую табличку, чтобы мозг не пытался хранить правило в виде «чувствую, что так надо»:
| Что это | Где используется | Как выглядит |
|---|---|---|
| Внешнее имя (label) | в месте вызова | |
| Внутреннее имя | внутри |
|
Когда внешнее и внутреннее имена совпадают
Вот пример функции, где внешние и внутренние имена совпадают:
func greet(name: String) -> String {
return "Привет, \(name)!"
}
print(greet(name: "Анна")) // Привет, Анна!
Когда имена разные
А вот пример, где в объявлении мы явно задаём внешний и внутренний варианты:
func greet(to name: String) -> String {
return "Привет, \(name)!"
}
print(greet(to: "Анна")) // Привет, Анна!
Обратите внимание: внутри функции переменная называется name, а снаружи мы вызываем её как to:. Вызов читается как «greet to Анна», то есть «поприветствуй Анну». Это почти как английский, но без обязательной грамматики и с меньшим количеством неправильных глаголов.
Такой стиль напрямую поддерживает идею «читаем как предложение» и соответствует Swift API Design Guidelines: метки аргументов должны помогать чтению и не заставлять пользователя додумывать смысл параметров.
3. Когда стоит убирать label через _
Иногда label действительно лишний. Если параметр — главный «объект действия», и смысл очевиден, Swift позволяет убрать внешний label с помощью _. Это делает вызов короче, но не должно делать его загадочнее.
Классический пример: функция «квадрат числа». Если мы написали square(x: 6), это не ошибка, но чуть многословно. Обычно пишут так:
func square(_ x: Int) -> Int {
return x * x
}
print(square(6)) // 36
Но вот где _ начинает вредить — это когда параметров несколько, особенно если они одного типа. Тогда f(10, 20) перестаёт быть кодом и становится «двумя числами, которые я почему-то отправил в космос».
Сравните:
func distance(_ start: Int, _ end: Int) -> Int {
return end - start
}
print(distance(3, 10)) // 7
и более читаемый вариант:
func distance(from start: Int, to end: Int) -> Int {
return end - start
}
print(distance(from: 3, to: 10)) // 7
Во втором случае вы не просто видите два числа, вы видите отношения между ними: from и to. И это как раз тот случай, когда «лишние буквы» экономят время на понимание.
Практическое правило на сегодня простое: убирайте label через _ только тогда, когда без него вызов остаётся абсолютно очевидным. Если вам приходится помнить порядок аргументов — значит label должен быть.
4. Как выбрать имена снаружи и внутри
Иногда хочется, чтобы снаружи функция читалась красиво, а внутри у вас были «нормальные» имена переменных, без предлогов и странных конструкций. Для этого и существует возможность задавать два имени.
Посмотрим на пример «проверить, лежит ли число в диапазоне». Снаружи мы хотим увидеть что-то вроде «значение в диапазоне от ... до ...». Внутри же нам удобнее оперировать minValue и maxValue.
func isInRange(_ value: Int, from minValue: Int, to maxValue: Int) -> Bool {
return value >= minValue && value <= maxValue
}
print(isInRange(7, from: 1, to: 10)) // true
Здесь сделано сразу несколько вещей «по-людски».
Вызов начинается с главного объекта — isInRange(7, from: …), поэтому первый параметр без label. А вот границы диапазона обязательно подписаны, иначе isInRange(7, 1, 10) выглядит как «7, 1, 10 — угадай, что есть что».
Ещё один полезный приём — использовать внешние слова, которые делают вызов похожим на фразу. При этом внутри можно оставить короткое имя, чтобы код не выглядел как сочинение на 10 страниц:
func makeGreeting(for personName: String) -> String {
return "Здравствуйте, \(personName)!"
}
print(makeGreeting(for: "Миша")) // Здравствуйте, Миша!
Снаружи мы видим for:, то есть «приветствие для кого», а внутри используем personName — понятное рабочее имя.
Labels-ключевые слова
Если вы смотрели на вызовы стандартной библиотеки, вы могли заметить метки вроде in, for, to, by. Возникает вопрос: «Подождите, но in — это же ключевое слово в циклах, как это вообще легально?»
В Swift это легально: большинство ключевых слов разрешены как argument labels (иначе многие естественные API пришлось бы называть странными словами‑заменителями). Это закреплено на уровне дизайна языка: идея как раз в том, чтобы вызовы читались естественно.
Давайте сделаем мини‑пример, который компилируется и выглядит как «английская фраза», даже если вы не фанат английского:
func isAffordable(_ price: Double, in budget: Double) -> Bool {
return price <= budget
}
print(isAffordable(99.9, in: 120.0)) // true
Здесь in: — хороший label. Он даёт смысл: «доступно ли по цене в рамках бюджета». И самое приятное: при вызове видно, что второе число — именно бюджет, а не «ещё одно число такого же типа, которое я случайно передал».
Связка «хорошие labels + хорошие имена функций» — это практически мини‑документация прямо в коде. И это не философия, а банальная экономия времени на чтение.
5. Пример: консольное приложение BudgetBuddy
Сейчас соберём небольшой пример, который можно использовать как «наш текущий учебный проект». Пусть это будет простая консольная программа BudgetBuddy: она спрашивает бюджет, цену товара и налог, считает итоговую стоимость и говорит, влезаем мы в бюджет или нет. Никаких массивов, классов и прочих будущих радостей — только то, что уже проходили.
Начнём с маленькой функции ввода. Здесь label особенно полезен: мы хотим читать вызов как «прочитай число с подсказкой».
func readDouble(prompt: String) -> Double {
print(prompt, terminator: ": ")
return Double(readLine() ?? "") ?? 0
}
let budget = readDouble(prompt: "Введите бюджет")
print(budget) // например: 100.0
Обратите внимание: prompt: — хороший label, потому что он отвечает на вопрос «что это за строка?».
Теперь вынесем расчёт итоговой цены. Тут нам хочется, чтобы вызов был максимально прозрачным. Вариант «без labels» быстро становится нечитабельным, поэтому оставим метки:
func totalPrice(for price: Double, taxPercent percent: Double) -> Double {
return price + price * percent / 100
}
let total = totalPrice(for: 80, taxPercent: 20)
print(total) // 96.0
Здесь мы сделали две важные вещи. Во-первых, внешний label for: помогает читать «итоговая цена для цены X». Во-вторых, для второго параметра мы используем внешнее taxPercent, а внутреннее — короткое percent, чтобы внутри формулы не было длинного имени (формула и так не мечта гуманитария).
Осталось сравнить с бюджетом. И вот тут прекрасно ложится label in::
func isAffordable(_ price: Double, in budget: Double) -> Bool {
return price <= budget
}
print(isAffordable(96, in: 100)) // true
Соберём всё в небольшой main‑кусок. Это всё ещё top‑level код, как в Web‑IDE, и компилируется:
func readDouble(prompt: String) -> Double {
print(prompt, terminator: ": ")
return Double(readLine() ?? "") ?? 0
}
func totalPrice(for price: Double, taxPercent percent: Double) -> Double {
return price + price * percent / 100
}
func isAffordable(_ price: Double, in budget: Double) -> Bool {
return price <= budget
}
let budget = readDouble(prompt: "Бюджет")
let price = readDouble(prompt: "Цена товара")
let tax = readDouble(prompt: "Налог, %")
let total = totalPrice(for: price, taxPercent: tax)
print("Итоговая цена: \(total)")
// Например: Итоговая цена: 96.0
if isAffordable(total, in: budget) {
print("Покупка влезает в бюджет.") // например: Покупка влезает в бюджет.
} else {
print("Не влезает в бюджет.") // например: Не влезает в бюджет.
}
Заметьте интересную вещь: если вы откроете этот код через месяц, вы поймёте его почти без комментариев — именно потому, что вызовы функций сами объясняют смысл аргументов.
6. Ошибки labels и подсказки IDE
Поскольку labels являются частью контракта вызова, компилятор очень строго следит за ними. Это иногда раздражает новичков: «Ну ты же понял, что я хотел передать вторым параметром, чего ты придираешься?» Но придирки тут как ремень безопасности: они мешают только до первого реального столкновения.
Например, если функция ожидает bar: как label, а вы вызываете без него, Swift даст ошибку вида missing argument label ... in call. Такие диагностики — нормальная часть опыта разработки: компилятор подсказывает, какой именно label вы забыли.
Представим, что вы забыли label prompt::
func readDouble(prompt: String) -> Double {
print(prompt, terminator: ": ")
return Double(readLine() ?? "") ?? 0
}
readDouble("Бюджет") // ❌ Ошибка: missing argument label 'prompt:' in call
Это не вредность Swift, а защита от ситуации, когда у вас 3–4 параметра одного типа, и вы случайно вызываете не так, как думали.
И вот ещё важная мысль: labels помогают не только компилятору, но и автодополнению в IDE. Когда вы пишете totalPrice(, IDE сразу подсказывает for: и taxPercent: — и вы уже меньше ошибаетесь чисто механически.
7. Типичные ошибки при работе с argument labels
Ошибка №1: убирать labels «для красоты», а потом путаться в аргументах.
Очень частая история: человек видит, что можно писать f(10, 20) и делает так везде, потому что короче. Через день выясняется, что он уже не помнит, где ширина, где высота, где начало, где конец. Если параметры одинакового типа и смысл не очевиден, labels — это не роскошь, а способ не превращать код в загадку.
Ошибка №2: путать внешнее и внутреннее имя параметра.
Если вы объявили func greet(to name: String), то при вызове нужно писать greet(to: "Анна"), а не greet(name: "Анна"). Внутреннее имя name существует для тела функции, а внешний label to: — для читаемого вызова. Когда мозг это путает, кажется, что компилятор «придирается к словам», но на самом деле он защищает контракт API.
Ошибка №3: делать вызов вида f(1, 2, 3) и надеяться, что читатель догадается.
Такой стиль может сойти в математической библиотеке, где у всех однозначные правила, но в прикладном коде это почти всегда ухудшает понимание. Если без label нельзя понять смысл аргумента без просмотра объявления функции — значит, label нужен.
Ошибка №4: делать слишком длинные labels, которые не добавляют смысла.
Иногда новички стараются «максимально описать всё» и получают что-то вроде totalPrice(forPrice:taxPercentValue:). Это перегиб. Label должен добавлять смысл, а не дублировать имя функции. Хороший сигнал качества: вызов читается как фраза и не выглядит как тавтология.
Ошибка №5: игнорировать стиль «читаем как предложение» и ставить случайные слова.
Labels вроде a:, b:, x1:, x2: не помогают чтению и обычно означают, что функция плохо спроектирована (или вы просто торопились). Даже в учебных задачах лучше приучать себя к говорящим меткам: from/to, in, for, min/max — потому что потом этот навык переносится на реальный код почти без усилий.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ