Símbolos en Ruby: qué son y en qué se diferencian de String
Un símbolo en Ruby es un nombre que no cambia. Se escribe con dos puntos delante: :ana, :nombre, :activo. Durante toda la ejecución del programa, Ruby guarda un único objeto por cada nombre: escribas :ana donde lo escribas, es siempre el mismo.
Piensa en una etiqueta impresa y unas hojas de notas. La etiqueta «Facturas» es una sola y no la reescribes; las hojas (el texto) las puedes tachar, ampliar o sustituir. Los símbolos son las etiquetas; los String, las hojas.
Símbolo frente a String: la diferencia en código
Cuando escribes dos cadenas iguales, aunque tengan exactamente lo mismo, Ruby crea dos objetos. En cambio, si escribes dos símbolos iguales, Ruby no crea otro: reutiliza el que ya existía.
Lo comprobamos con object_id, el número que identifica a cada objeto dentro del programa:
cadena1 = "ana"
cadena2 = "ana"
simbolo1 = :ana
simbolo2 = :ana
puts cadena1.object_id # 16
puts cadena2.object_id # 24 (otro objeto)
puts simbolo1.object_id # 16101644 (el número cambia según la versión)
puts simbolo2.object_id # 16101644 (el mismo objeto)
Los números concretos cambian en cada ejecución y en cada versión de Ruby; lo que importa es si coinciden o no.
Igual contenido no es el mismo objeto
Ruby distingue dos preguntas:
==: ¿tienen el mismo contenido?equal?: ¿son el mismo objeto?
p cadena1 == cadena2 # true: mismo texto
p cadena1.equal?(cadena2) # false: dos objetos distintos
p simbolo1.equal?(simbolo2) # true: un único objeto
Para saber si dos cadenas son iguales, Ruby tiene que comparar su contenido. Con dos símbolos le basta con mirar si son el mismo objeto, porque nunca habrá dos símbolos distintos con el mismo nombre. Es como comprobar si dos llaves son iguales: con cadenas miras los dientes uno a uno; con símbolos, basta con ver si es la misma llave.
Un símbolo no se puede modificar
Un String se puede cambiar después de crearlo. Un símbolo, no: está siempre congelado (en Ruby, un objeto congelado o frozen es uno que no admite cambios).
nombre = "ana"
nombre << " lópez"
p nombre # "ana lópez"
p :ana.frozen? # true
:ana.upcase! # NoMethodError: undefined method 'upcase!' for an instance of Symbol
Eso no significa que un símbolo no tenga métodos. Tiene los que no modifican nada, y devuelven un valor nuevo:
p :ana.length # 3
p :ana.upcase # :ANA (otro símbolo)
p :ana.to_s # "ana" (de símbolo a String)
p "ana".to_sym # :ana (de String a símbolo)
p :"con espacios" # también se pueden crear con comillas
p %i[uno dos tres] # [:uno, :dos, :tres]
Símbolo :ana
- Un solo objeto por nombre
- Siempre congelado
- Para nombrar cosas: claves, opciones, estados
String "ana"
- Puede haber muchos objetos con el mismo texto
- Modificable, salvo que lo congeles
- Para el texto con el que trabajas: nombres de usuario, mensajes, contenido
Símbolos como claves de un hash
El uso más habitual de los símbolos es como claves de un hash, la estructura de Ruby que guarda pares clave-valor (lo tienes explicado en Hashes en Ruby):
usuario = { nombre: "Ana", lenguaje: "Ruby" }
p usuario[:nombre] # "Ana"
p usuario["nombre"] # nil: la clave es un símbolo, no un String
La sintaxis nombre: "Ana" es la forma corta de :nombre => "Ana"; las dos crean una clave de tipo símbolo.
También puedes usar String como claves. Es seguro: cuando usas un String sin congelar como clave, Ruby guarda una copia congelada, así que cambiar la variable original no estropea el hash.
¿Cuándo se deben utilizar símbolos?
- Cuando no necesitas modificar el texto.
- Cuando el valor es un nombre de algo y no contenido: claves de hash, opciones de un método, estados como
:activoo:pendiente. - Cuando vas a comparar ese valor muchas veces.
Usa String cuando el texto viene de fuera (lo escribe el usuario, llega de un fichero o de una API) o cuando lo vas a transformar.
Símbolos para nombrar; String para el texto.
¿Ocupan memoria para siempre los símbolos?
Durante años se recomendaba no convertir en símbolos el texto que llega de fuera, porque los símbolos nunca se liberaban y la memoria podía crecer sin límite. Desde Ruby 2.2, la mayoría de los símbolos creados con to_sym o intern los puede liberar el recolector de basura (la parte de Ruby que libera la memoria que ya no se usa). Los que escribes directamente en el código, como :nombre, siguen viviendo mientras dure el programa.
Lo comprobamos creando mil símbolos y forzando una recolección:
GC.disable
antes = Symbol.all_symbols.size
1000.times { |i| "dinamico_#{i}".to_sym }
p Symbol.all_symbols.size - antes # 1000
GC.enable
GC.start
p Symbol.all_symbols.size - antes # 0: se han liberado
Aun así, crear símbolos a partir de lo que escribe un usuario rara vez tiene sentido: si es texto que viene de fuera, trátalo como String.
Cadenas congeladas: frozen_string_literal
Los símbolos son siempre inmutables; los String, no. Pero Ruby está cambiando cómo trata los String que escribes entre comillas en el código (los literales).
El comentario mágico
Si pones este comentario en la primera línea de un fichero, todos los literales de ese fichero quedan congelados:
# frozen_string_literal: true
s = "hola"
p s.frozen? # true
p "hola".equal?("hola") # true: Ruby reutiliza el mismo objeto
s << " mundo" # FrozenError: can't modify frozen String: "hola"
Con el comentario, los literales se parecen un poco más a los símbolos: el mismo texto es el mismo objeto y no se puede modificar. Hay dos excepciones útiles:
# frozen_string_literal: true
mutable = +"hola" # + devuelve una copia modificable
mutable << " mundo"
p mutable # "hola mundo"
p "x#{1}".frozen? # false: con interpolación no se congela
El comentario debe estar en la primera línea del fichero, o en la segunda si la primera es un shebang (#!/usr/bin/env ruby). Sobre la interpolación tienes más en Interpolación de cadenas en Ruby.
Sin el comentario: el aviso de Ruby 3.4 y 4.0
Desde Ruby 3.4, en los ficheros sin ese comentario, los literales se pueden seguir modificando, pero Ruby avisa de que dejará de permitirlo. El aviso solo aparece si activas las advertencias de obsolescencia con -W:deprecated:
ruby -W:deprecated literal.rb
literal.rb:3: warning: literal string will be frozen in the future (run with --debug-frozen-string-literal for more information)
En este estado, "hola".frozen? sigue devolviendo false. Con --enable=frozen-string-literal congelas todos los literales sin poner el comentario fichero a fichero, y con --disable=frozen-string-literal desactivas el aviso.
Lo mismo pasa con el texto que devuelve Symbol#to_s: en Ruby 4.0 avisa si lo modificas, y en las notas de la próxima versión (4.1, todavía en desarrollo) ya figura que devolverá un String congelado.
Resumen
| Símbolo | String | |
|---|---|---|
| Se escribe | :ana |
"ana" |
| Objetos con el mismo nombre | Siempre uno | Uno por literal (salvo con frozen_string_literal: true) |
| ¿Se puede modificar? | No, nunca | Sí, salvo que esté congelado |
| Comparación | ¿Es el mismo objeto? | Compara el contenido |
| Uso típico | Claves de hash, opciones, estados | Texto de usuario, mensajes, ficheros |
| Convertir | :ana.to_s |
"ana".to_sym |
Fuentes
Comprobado el 7 de noviembre de 2026 en la documentación oficial de Ruby:
- Symbol (Ruby 4.0): un mismo objeto por nombre durante toda la ejecución.
- String (Ruby 4.0):
String#+@,freezeydup. - Hash (Ruby 4.0): sintaxis de claves y copia congelada de las claves String.
- Comentarios mágicos (Ruby 4.0):
frozen_string_literal, su posición y la interpolación. - NEWS de Ruby 2.2 (recolección de símbolos), Ruby 3.4 (aviso al modificar literales y
Symbol#to_s), Ruby 4.0 y Ruby 4.1 en desarrollo (Symbol#to_scongelado).
Todos los ejemplos se han ejecutado con Ruby 4.0.9 (imagen oficial ruby:latest de Docker) y las salidas de los comentarios son las reales.