val и var: неизменяемость по умолчанию

В Scala выбор между val и var — это маленькое решение с большими последствиями для надёжности кода.

«Неизменяемость — это не ограничение, а суперсила: то, что не может измениться, не может сломаться неожиданно.»

В большинстве языков переменная — это коробка, в которую можно класть разные значения. В Scala есть два вида «коробок». val — это коробка, которую запаяли: положили значение один раз и больше не меняем. var — обычная переменная, содержимое можно перезаписывать.

val pi = 3.14      // нельзя переприсвоить
var counter = 0    // можно менять
counter = counter + 1   // ок
// pi = 3.15        // ОШИБКА компиляции!

Почему это важно

Когда вы видите val, вы точно знаете: это значение никогда не изменится. Не нужно держать в голове, кто и где мог его перезаписать. В больших программах это огромная экономия внимания. Поэтому правило Scala: по умолчанию используй val, а var — только когда без изменения действительно не обойтись.

Типы можно не писать

Scala умеет выводить тип сама (type inference). Но при желании тип указывают явно после двоеточия:

val age: Int = 25
val price: Double = 9.99
val title: String = "Scala"
val ready: Boolean = true

Та же идея на Python ▶

# В Python нет val/var, но идею неизменяемости передаёт кортеж
# и соглашение об именах (КОНСТАНТЫ в верхнем регистре)
PI = 3.14            # по соглашению не меняем (как val)
counter = 0          # обычная переменная (как var)
counter = counter + 1
print(PI, counter)

# Настоящая неизменяемость — кортеж
point = (10, 20)     # элементы менять нельзя

Неизменяемые ссылки и неизменяемые данные

Важный нюанс: val запрещает переприсваивать ссылку, но если внутри сидит изменяемый объект, его содержимое всё ещё можно поменять. Поэтому в Scala предпочитают не только val, но и неизменяемые коллекции — о них позже.

val x = коробка -> [значение 5]
  переприсвоить x  ->  ЗАПРЕЩЕНО

var y = коробка -> [значение 5]
  y = 7  ->  коробка -> [значение 7]  ОК

Как работает под капотом (JVM)

На уровне JVM val часто компилируется в final-поле или локальную переменную, которую нельзя переприсвоить. JVM любит final: такие значения проще оптимизировать и безопаснее использовать в многопоточном коде, потому что они гарантированно не «прыгают» под ногами у других потоков. var же становится обычным изменяемым полем.

Частые ошибки

  • Лепить var везде по привычке из других языков. В Scala это сигнал «здесь что-то меняется» — и часто его можно избежать.
  • Думать, что val с изменяемым списком внутри полностью неизменяем. Ссылка зафиксирована, а содержимое — нет.
  • Бояться писать типы. Иногда явный тип делает код понятнее, особенно в сигнатурах функций.

Best practices

  • Начинайте всегда с val. Меняйте на var только если компилятор или логика вынуждают.
  • Указывайте типы для публичных значений и параметров функций — это документация.
  • Сочетайте val с неизменяемыми коллекциями для полной безопасности.

Неизменяемость как стиль мышления

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

Особенно ярко выгода проявляется в многопоточных программах. Если данные не меняются, их можно безопасно читать из множества потоков одновременно — никаких блокировок, никаких гонок. Именно поэтому функциональный стиль так хорошо ложится на современные многоядерные системы. Начиная с привычки к val, вы закладываете фундамент для масштабируемого и безопасного кода, даже если пока пишете простые однопоточные программы.

Хороший практический ориентир: если в коде появляется var, остановитесь и спросите, нельзя ли выразить ту же логику через преобразование неизменяемых значений. Часто ответ — да, и результат оказывается чище. Изменяемое состояние не запрещено, но в Scala оно должно быть осознанным выбором, а не значением по умолчанию из привычки.

Итоги. val — неизменяемое значение (предпочтительно), var — изменяемая переменная (по нужде). Неизменяемость делает код предсказуемым. Дальше — система типов Scala целиком.

Проверьте себя
1. В чём разница между val и var?
Aval для чисел, var для строк
Bval нельзя переприсвоить, var можно
Cvar быстрее работает
Dval виден только внутри функции
2. Что произойдёт с этим кодом: val x = 5; x = 6?
Ax станет равен 6
BОшибка компиляции — нельзя переприсвоить val
Cx останется 5, присваивание проигнорируется
DПрограмма упадёт во время выполнения