1. Программе всегда нужен ввод
Когда вы впервые видите консольную задачу, кажется, что программа — это как микроволновка: нажал кнопку, она что-то сделала. Но большинство задач устроены иначе: программа должна реагировать на входные данные. В реальности это может быть пользователь, тестовая система или файл, но принцип один — программа получает данные извне и только потом решает, что печатать.
Представим самый честный сценарий. Вы хотите программу, которая приветствует человека по имени. Если имя зашито в коде, это приветствие будет одинаковым для всех (как табличка «Добро пожаловать, дорогой пользователь №42»). Значит, нам нужен ввод.
В консольных задачах обычно есть поток:
flowchart LR
A[Вход: строки] --> B[Программа Swift]
B --> C[Выход: строки]
И сегодня мы займёмся самым первым шагом — «вход: строки».
2. readLine() и String?
readLine() как «прочитай одну строку и верни мне её»
readLine() — это базовая функция для чтения ввода в консольных задачах. Её философия простая: «дай мне одну строку текста». Это прямо совпадает с тем, как большинство онлайн-проверок подают вход: набор строк, каждая заканчивается переводом строки.
Важно понимать поведение на уровне «как это ощущается»:
- программа ждёт, пока появится строка;
- строка обычно заканчивается нажатием Enter (в интерактивной консоли);
- readLine() возвращает то, что было введено, без символа перевода строки.
Мини-пример: читаем одну строку и печатаем:
let line = readLine()
print(line) // если ввести "Hello", часто увидите Optional("Hello")
Пример на две строки ввода: программа читает два раза — значит, ждёт две строки:
let first = readLine()
let second = readLine()
print(first) // например Optional("Alice")
print(second) // например Optional("Bob")
Обратите внимание: мы пока ничего «не обрабатываем», а только наблюдаем. Это нормально. Цель — понять тип String? и слово nil, а не «победить все ошибки ввода» в первый же день.
Почему результат вызова readLine() — это String?, а не String
Вот здесь начинается самое интересное. Многие ожидают, что раз мы «читаем строку», то функция должна вернуть String. Но Swift говорит: «А вдруг строки нет?»
И это не паранойя языка, а реальность ввода. Ввод может закончиться. Например:
- тестовая система дала меньше строк, чем вы ожидали;
- пользователь закрыл ввод (в терминале это бывает);
- поток ввода пустой.
Когда строку прочитать невозможно, readLine() возвращает отсутствие значения. В Swift отсутствие значения выражается словом nil.
Идея такая:
- строка есть → возвращаем её;
- строки нет → возвращаем nil.
Именно поэтому тип — String?. Знак вопроса в конце типа буквально означает: «значение может отсутствовать».
Можно представить это как автомат с жвачкой: вы тянете ручку (readLine()), но не всегда внутри есть жвачка. Иногда там пусто, и автомат честно говорит «пусто» (nil), вместо того чтобы выдавать вам воздух в красивой упаковке.
3. Optional: модель «значение есть / значения нет»
Когда вы видите String?, читайте это так: «опциональная строка». Это контейнер, который либо хранит строку, либо ничего не хранит. В Swift это настолько важная идея, что она встроена в язык, а не оформлена как «библиотечная фишка».
На уровне базовой интуиции:
- String — строка обязательна, она точно есть;
- String? — строка возможна, но не гарантирована.
Внутри языка это часто объясняют так: Optional — это перечисление с двумя состояниями: «есть значение» и «нет значения». То есть случай nil — полноценная ветка, которую нужно учитывать.
Создадим Optional руками, без ввода:
let maybeName: String? = "Sam"
let nothing: String? = nil
print(maybeName) // Optional("Sam")
print(nothing) // nil
Важный психологический момент: nil — это не «пустая строка» и не «0». Это именно «значения нет». Пустая строка — это "" (строка есть, просто в ней ноль символов). А nil — это отсутствие самой строки.
String и String? — разные типы
Когда начинающий видит String?, очень хочется думать: «Ну это же почти String». И вот тут Swift включает режим «строгий, но справедливый преподаватель»: это не «почти», это другой тип.
Причина простая: со String вы можете делать строковые операции, потому что строка гарантированно существует. Со String? нельзя делать вид, что строка есть — потому что в состоянии nil никаких символов нет, и обращаться не к чему.
В этой лекции мы не будем разворачивать optional в обычную строку (для этого позже появятся специальные конструкции). Но уже сейчас полезно привыкнуть к идее: ? в типе — это красный флажок «здесь возможна пустота».
Удобная табличка различий:
| Тип | Может быть nil | Гарантированно есть строка |
|---|---|---|
|
нет | да |
|
да | нет |
Если вы поймаете себя на мысли «да какая разница», представьте, что String? — это посылка, которая может быть пустой. Пока вы не проверили, что внутри, нельзя уверенно говорить «там точно лежит строка».
Иногда при печати вы видите Optional("...")
На практике первое, что смущает: «Я ввёл Alex, а программа печатает Optional("Alex")». Возникает ощущение, что Swift ещё и троллит.
На самом деле это подсказка: вы печатаете не строку, а опциональную строку. И Swift показывает это честно, чтобы вы не забывали про возможность nil.
Есть даже идея на уровне обсуждений языка: ситуация, когда Optional передают туда, где ожидается Any (а print принимает значения именно как Any), настолько частая, что язык мог бы предупреждать об этом заметнее. Проще говоря: «если ты печатаешь optional — возможно, ты забыл его обработать».
Пример:
print("Введите имя:")
let name = readLine()
print("Привет, \(name)")
// если ввести "Alex", часто будет: Привет, Optional("Alex")
Почему так? Потому что интерполяция \(name) видит, что name — String?, и превращает его в строку «как умеет», не теряя информации о том, что это optional. Поэтому вы и видите оболочку.
И да, это пока нормально. Мы сегодня не делаем «красивый вывод», мы учимся видеть причину: readLine() может вернуть nil, поэтому тип такой, и печать напоминает об этом.
4. Мини‑пример: ConsoleBuddy читает ввод
Сейчас соберём маленький учебный проект (пока в одном файле, в стиле Web‑IDE): консольный помощник, который задаёт пару вопросов. Он ещё не будет «умным», но он уже будет интерактивным — то есть перестанет быть монологом.
Код намеренно простой: читаем две строки и печатаем их как есть (с optional-оболочкой). Это не баг, это учебный этап.
print("ConsoleBuddy v0.1")
print("Как тебя зовут?")
let name = readLine()
print("Из какого ты города?")
let city = readLine()
print("Рад знакомству! name=\(name), city=\(city)")
// Пример вывода при вводе строк:
// Рад знакомству! name=Optional("Alex"), city=Optional("Boston")
Что важно заметить: мы не используем ни условий, ни циклов, ни «опасных» приёмов. Программа идёт сверху вниз, как вы привыкли. Но теперь в середине этого «сверху вниз» есть точки, где она ждёт внешний мир.
Если вы тестируете в Web‑IDE, обычно нужно добавить две строки входа, например:
Alex
Boston
И тогда readLine() два раза подряд получит по строке.
5. Как правильно думать про ввод: строка может не прийти
Сейчас хочется «скорее сделать красиво», но намного важнее заложить привычку: любой ввод — это данные из внешнего мира, а внешний мир не подписывал контракт «всегда вести себя идеально».
В Swift это выражается не нравоучениями, а типами. Если тип String?, значит, у вас в руках значение, которое может отсутствовать. И это не «редкий случай», а сценарий, который язык заставляет держать в голове всегда.
Можно представить это как два состояния, в которых мы можем оказаться после readLine():
stateDiagram-v2
[*] --> ReadAttempt: вызываем readLine()
ReadAttempt --> GotText: строка прочитана
ReadAttempt --> GotNil: ввода нет (nil)
На уровне мышления это означает простую вещь: после readLine() вы пока не можете с уверенностью сказать «у меня есть строка». Вы можете сказать: «у меня возможно есть строка».
6. Типичные ошибки при работе с readLine() и Optional
Ошибка №1: считать, что readLine() «всегда что‑то возвращает».
Проблема тут даже не в том, что программа «сломается» прямо сегодня (мы пока только печатаем optional и ничего не разворачиваем). Проблема в привычке: если вы мысленно привыкли, что ввод «обязан быть», дальше вы начнёте писать код, который падает или ведёт себя странно на пустом вводе. Swift специально даёт String?, чтобы вы так не делали.
Ошибка №2: путать nil и пустую строку "".
Пустая строка означает: строка существует, просто в ней нет символов (например, пользователь нажал Enter, ничего не введя). nil означает: строки не было вообще (например, ввод закончился). Это разные ситуации, и позже мы научимся различать их в логике программы.
Ошибка №3: удивляться выводу Optional("...") и пытаться «пофиксить любой ценой».
Типичная реакция: «некрасиво, хочу просто Alex». Но сейчас важнее понять, почему так печатается: вы печатаете optional, и Swift честно показывает оболочку. Дальше мы будем разбирать способы получить обычную строку и что делать с nil, но сегодня наша цель — увидеть причину, а не замазать симптом.
Ошибка №4: писать много readLine() подряд и терять соответствие «какая строка за что отвечает».
Когда в программе три-четыре чтения подряд, легко перепутать, где имя, где город, где ещё что-то. Уже сейчас помогает дисциплина: перед каждым чтением печатать подсказку print("...") и давать переменным понятные имена. Консольная программа без подсказок — это как диалог, где собеседник молчит и только иногда записывает что-то в блокнот. Немного тревожно.
Ошибка №5: воспринимать Optional как «странную штуку Swift» вместо нормальной модели реальности.
Optional — это не «сложность ради сложности». Это способ заставить вас учитывать отсутствие данных. Ввод — первая точка, где вы встречаете это вживую, но сама идея встречается дальше очень часто: везде, где значение может отсутствовать по смыслу задачи (и язык помогает вам это не забыть).
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ