Строки и символы

Строки в Ruby — мощные и гибкие объекты с сотней встроенных методов. А рядом живут символы — лёгкие неизменяемые «имена», которые часто путают со строками.
Суть: строка (String) — это изменяемая последовательность символов с богатым набором методов; символ (Symbol, пишется :имя) — неизменяемый уникальный идентификатор, который дешевле строки и используется как ключ и как имя метода.

Строка — один из самых используемых типов. В Ruby их можно создавать в одинарных и двойных кавычках, склеивать, резать, искать в них подстроки и преобразовывать десятками способов. Двойные кавычки поддерживают интерполяцию #{} и escape-последовательности вроде \n, одинарные — нет, зато работают чуть быстрее и буквальнее.

s = "Ruby для начинающих"
puts s.length            # => 19
puts s.upcase            # => RUBY ДЛЯ НАЧИНАЮЩИХ
puts s.split(" ").first  # => Ruby
puts s.include?("для")   # => true
puts s.gsub("Ruby", "Go") # => Go для начинающих

Разбор: изменяемость строк

В отличие от многих языков, строки в Ruby по умолчанию изменяемы: метод upcase! с восклицательным знаком меняет саму строку, а upcase без него возвращает новую копию. Это соглашение «методы с ! опасны / изменяют объект» пронизывает весь язык.

name = "ruby"
name.upcase       # => "RUBY", но name всё ещё "ruby"
name.upcase!      # name теперь "RUBY" — изменили на месте
puts name         # => RUBY

Разбор: зачем нужны символы

Символ :status похож на строку "status", но это другой объект. Ключевое отличие: для каждого уникального символа в программе существует ровно один экземпляр в памяти, и он неизменяем. Поэтому символы идеальны как ключи хэшей и имена — их сравнение мгновенно, а памяти они почти не тратят.

строки                       символы
"name" в строке 1            :name в строке 1  ---+
"name" в строке 2                                +--> ОДИН объект :name
  (могут быть РАЗНЫЕ          :name в строке 2  ---+
   объекты в памяти)               (всегда ОДИН и тот же)
puts :name.object_id == :name.object_id   # => true  (один объект)
puts "n".object_id  == "n".object_id      # => false (разные объекты)
user = { name: "Аня", role: :admin }      # ключи-символы
puts user[:name]                          # => Аня

Как работает под капотом

Когда Ruby встречает символ :status, он заносит его в внутреннюю таблицу символов один раз. Повторные упоминания возвращают ссылку на тот же объект. Строки же при каждом литерале (без оптимизаций) могут создаваться заново. Именно поэтому в Ruby 3.4 появилось «строки ведут себя как замороженные по умолчанию» — оптимизация, при которой одинаковые строковые литералы могут переиспользоваться, снижая нагрузку на сборщик мусора.

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

  • Смешивать ключи-строки и ключи-символы. { "name" => "Аня" }[:name] вернёт nil: строка и символ — разные ключи.
  • Забывать про разницу версий с !. arr.sort возвращает новый массив, arr.sort! меняет исходный. Перепутали — потеряли данные.
  • Конкатенация в цикле. Тысячи += создают много мусорных строк; для сборки текста используйте массив и join.

Best practices

  • Используйте символы для фиксированных «меток»: статусов, ролей, ключей хэшей.
  • Предпочитайте интерполяцию "Привет, #{name}" конкатенации "Привет, " + name — она читабельнее и не падает на не-строках.
  • Добавляйте # frozen_string_literal: true в начало файлов — это включает заморозку строк и ускоряет программу.

Глубже: кодировки и заморозка строк

Ещё две темы вокруг строк, которые отличают новичка от уверенного рубиста. Первая — кодировки. Современный Ruby по умолчанию работает в UTF-8, и метод length честно считает символы, а не байты: для строки с кириллицей или эмодзи вы получите число букв, а не объём памяти. Если же нужны именно байты, есть bytesize. Это избавляет от целого класса ошибок, мучивших программистов в эпоху до повсеместного Unicode. Вторая тема — заморозка строк. Магический комментарий # frozen_string_literal: true в начале файла делает все строковые литералы неизменяемыми. Это даёт прирост производительности (одинаковые литералы переиспользуются, а не создаются заново) и защищает от случайных мутаций общих строк. В Ruby 3.4 язык сделал шаг к тому, чтобы такое поведение стало стандартным: мутация литерала без магического комментария теперь выдаёт предупреждение. Привыкайте добавлять этот комментарий в каждый файл — это бесплатная оптимизация и хороший тон.

Итог. Строки изменяемы и богаты методами, версии с ! меняют объект на месте. Символы — лёгкие неизменяемые идентификаторы, единственные в своём роде; берите их для ключей и меток.

Проверьте себя
1. Чем символ :name принципиально отличается от строки «name»?
AСимвол занимает больше памяти
BСимвол неизменяем и существует в единственном экземпляре в памяти
CСимвол нельзя использовать как ключ хэша
DМежду ними нет разницы
2. Что делает метод upcase! (с восклицательным знаком) по сравнению с upcase?
AНичего, это синонимы
Bupcase! изменяет саму строку на месте, а upcase возвращает новую копию
Cupcase! работает только с символами
Dupcase! всегда возвращает nil