惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

L
LangChain Blog
C
Check Point Blog
博客园 - Franky
V
Visual Studio Blog
云风的 BLOG
云风的 BLOG
aimingoo的专栏
aimingoo的专栏
Microsoft Security Blog
Microsoft Security Blog
V2EX - 技术
V2EX - 技术
AI
AI
Hacker News - Newest:
Hacker News - Newest: "LLM"
Jina AI
Jina AI
S
Security @ Cisco Blogs
Security Archives - TechRepublic
Security Archives - TechRepublic
H
Hacker News: Front Page
H
Hackread – Cybersecurity News, Data Breaches, AI and More
O
OpenAI News
Attack and Defense Labs
Attack and Defense Labs
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
爱范儿
爱范儿
H
Heimdal Security Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
G
Google Developers Blog
G
GRAHAM CLULEY
V
V2EX
The Register - Security
The Register - Security
人人都是产品经理
人人都是产品经理
B
Blog RSS Feed
Schneier on Security
Schneier on Security
M
MIT News - Artificial intelligence
Stack Overflow Blog
Stack Overflow Blog
Help Net Security
Help Net Security
大猫的无限游戏
大猫的无限游戏
C
CERT Recently Published Vulnerability Notes
The GitHub Blog
The GitHub Blog
V
Vulnerabilities – Threatpost
The Last Watchdog
The Last Watchdog
J
Java Code Geeks
S
Secure Thoughts
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
量子位
NISL@THU
NISL@THU
K
Kaspersky official blog
Engineering at Meta
Engineering at Meta
T
Threatpost
Recent Commits to openclaw:main
Recent Commits to openclaw:main
宝玉的分享
宝玉的分享
Security Latest
Security Latest
T
The Exploit Database - CXSecurity.com
博客园_首页
A
Arctic Wolf

Stonecharioteer on Tech

I Traced My Traffic Through a Home Tailscale Exit Node What Was I Reading Last? In Three Not-So-Easy Pieces Dogfooding Is Hard Code blocks in your books, finally GoForGo v0.9.0 Merrilin - We built an app to read books I use a Macbook now Data Structures & Algorithms - Preparing for Interviews Using a local DNS namespace for local service discovery Direction KOllector - Publishing KOReader Highlights gbt: branches touched in the last 24 hours A Soiree into Symbols in Ruby Some Smalltalk about Ruby Loops Ruby Blocks Returning from Ruby Blocks, Procs and Lambdas My Linux Laptop Finally Works: How Claude Helped Me Fix Years of Annoyances TIL: Watchexec - Modern File Watching for Development Workflows A Less Busy Mind GoForGo - Learn Go through live examples Migrating My Old Blog to Hugo with Claude The Qtile Window Manager: A Python-Powered Tiling Experience Read the RFCs that Built the Internet Py-x-Protobuf - Or How I Learned to Stop Worrying and Love Protocol Buffers Python Reverse a List New Beginnings Leaving ChainSafe Systems Screen Lock for Cinnamon Desktop using Zenity and Terminal Commands Crews Not Teams A System for Getting Better at LeetCode So Far So Rust Retrying HTTP Requests with Rust A Primer on Control Charts Learning Rust Explicit is Better than Implicit: Rust for Pythonistas Using Custom Delimiters in Jinja Templates TIL: Creating Fixed Length Iterables in Python Documentation Without Assumption Vagrant Python - A Reflection in 2022 Learning Golang No, A Virtual Machine Is Not Enough: Why Developers Need Native Linux Empathy in Tech For Those Who Came in Late A Weekend With PostgreSQL TIL: Gooey and Python Fire for Quick GUIs and CLIs TIL: 2ality - Dr. Axel Rauschmayer's JavaScript Blog TIL: MassDNS - High-Performance Bulk DNS Lookups TIL: Matomo Analytics, Google Tech Writing, Memory Programming, and NES TV Signals TIL: MontyDB - MongoDB Implemented in Python Returning to the Craft of Programming TIL: CPUFetch, OneFetch, and Learn CSS TIL: DNS Performance Testing and Pi-hole with Unbound TIL: Eli Bendersky's Blog, Awesome By Example, NoCoDB, and Martin Kleppmann TIL: CRDTs, Extreme HTTP Performance, and BYTEPATH Game TIL: AutoInvent, ASGI, Python Packaging, RAPIDS GPU Computing, and FlaskCon TIL: MangaDesk - Terminal Client for MangaDex TIL: McFly - Smart Shell History Search TIL: Siege Load Testing and Awesome FastAPI Resources TIL: Ventoy Bootable USB and Justniffer Network Analysis TIL: CLI Code Review, Git Split Diffs, and Internal Combustion Engine TIL: Benford's Law, Web Security Headers, Event Sourcing, and Mozilla Security Guidelines How to Write Documentation - The README.md File The Importance of Documentation TIL: NNgroup UX Research, SponsorBlock, and Labella Python Library TIL: The Little Book of Rust Macros and Rust Performance Book TIL: Git-Bug Distributed Issue Tracker and Omni Kubernetes Monitoring TIL: Zellij - Modern Terminal Multiplexer TIL: How Discord Handles 2.5 Million Concurrent Voice Users TIL: Volumio - The Audiophile Music Player TIL: Areopagitica - Milton's Defense of Free Speech TIL: Fast Node Manager, Zoxide Smart CD, Technical Writing, PyO3, and Qubes OS TIL: Slurm Workload Manager for HPC Clusters TIL: Data Visualization Guide and Oso Authorization Academy TIL: CORS Deep Dive, Piku Tiny PaaS, Rust Strings, and Deno Standard Library TIL: Raspberry Pi OS Development, Vim Beginner Guide, Password Management, and QueryBook TIL: uBlock Origin Performance Optimization on Firefox TIL: Breaking PostgreSQL at Scale and LeetCode Problem Patterns TIL: Awesome Tmux Resources for Terminal Multiplexing TIL: Grit - A Multitree-Based Personal Task Manager TIL: Lens 4.2 Kubernetes IDE, Shell Scripting Guide, and Dark HTTP Server Do The Job You Hate So You Won't Hate The Job You Love TIL: Innernet VPN Solution and NoteCalc Calculator App TIL: Argo CD for GitOps and Lens Kubernetes IDE TIL: Modern Rust CLI Tools - System Monitoring, HTTP Requests, and DNS TIL: tz - A Time Zone Helper Tool TIL: Distributed Systems Education, Fallacies, and Self-Hosted Internet Archiving TIL: Real-Time Voice Cloning Technology TIL: ChartMuseum for Helm, AMD's Corporate Journey, and Kubernetes Pod Scaling TIL: Docker and Kubernetes Tools - Whaler, Descheduler, and Dive TIL: Post-Mortem Collection, Terminal Plotting, and Technical Twitter TIL: Dark Mode Toggle Web Component by Google Chrome Labs TIL: Python eval(), exec(), and compile() Functions TIL: Camelot PDF Tables, PostgreSQL Row Level Security, Zerodha Varsity, and Write Yourself a Git TIL: fuser Command for Process and File Investigation TIL: i Hate Regex - The Ultimate Regex Cheat Sheet TIL: Dolt - Git for Data and Database Version Control TIL: x86 Assembly Programming and SafeEyes Break Reminder TIL: Comprehensive Distributed Systems Reading List TIL: Cosmopolitan C Library, Distributed Systems Book, High Performance Browser Networking, and Rust Roguelike Tutorial
A Soiree into Symbols in Ruby
2025-11-03 · via Stonecharioteer on Tech

Symbols and Why You Should Care

I should start, aptly, with the manual.

A Symbol object represents a named identifier inside the Ruby interpreter.

Okay, it’s a token of a sort, I think.

I’ve been befuddled when seeing :name instead of name and I didn’t know what it was at first. I spent some time reading Michael Hartl’s Learn Enough Ruby and I cannot really say I understood Symbols, so I wanted to RTFM.

The same Symbol object will be created for a given name or string for the duration of a program’s execution, regardless of the context or meaning of that name. Thus, if Fred is a constant in one context , a method in another, and a class in a third, the Symbol :Fred will be the same object in all three contexts.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
module One
  class Fred
  end
  $f1 = :Fred
end

module Two
  Fred = 1
  $f2 = :Fred
end

module Three
  def Fred()
  end
  $f3 = :Fred
end

puts $f1.object_id
puts $f2.object_id
puts $f3.object_id

Running this code shows:

18152204
18152204
18152204

All three symbols have the exact same object_id, proving they’re the same object in memory despite Fred being a class, a constant, and a method in different contexts.

Does that mean all three symbols are the exact same object? At first glance, :Fred seems to be the thing that identifies the class, variable or the method. But let’s expand our example to version 2, where each module has a method that uses the symbol :Fred to interact with whatever Fred means in that context.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
module One
  class Fred
    def greet
      "Hello from Fred the class!"
    end
  end

  def self.use_fred_symbol
    # Use the symbol :Fred to get the class
    klass = const_get(:Fred)
    instance = klass.new
    puts "In module One, using symbol :Fred to get the class"
    puts "const_get(:Fred) returns: #{klass.class}"
    puts "Creating instance: #{instance.greet}"
    :Fred  # Return the symbol
  end
end

module Two
  Fred = 42  # Fred is a constant

  def self.use_fred_symbol
    # Use the symbol :Fred to get the constant
    value = const_get(:Fred)
    result = value * 2
    puts "In module Two, using symbol :Fred to get the constant"
    puts "const_get(:Fred) returns: #{value.class} with value #{value}"
    puts "Calculating #{value} * 2 = #{result}"
    :Fred  # Return the symbol
  end
end

module Three
  def self.Fred()  # Fred is a method
    "I'm the Fred method!"
  end

  def self.use_fred_symbol
    # Use the symbol :Fred to call the method
    result = send(:Fred)
    puts "In module Three, using symbol :Fred to call the method"
    puts "send(:Fred) returns: #{result}"
    :Fred  # Return the symbol
  end
end

puts "=== Using the symbol :Fred in different contexts ==="
puts
sym1 = One.use_fred_symbol
puts "Returned symbol object_id: #{sym1.object_id}"
puts

sym2 = Two.use_fred_symbol
puts "Returned symbol object_id: #{sym2.object_id}"
puts

sym3 = Three.use_fred_symbol
puts "Returned symbol object_id: #{sym3.object_id}"
puts

puts "=== The symbol :Fred is the same everywhere ==="
puts "All three object_ids match: #{sym1.object_id == sym2.object_id && sym2.object_id == sym3.object_id}"
puts "sym1.equal?(sym2): #{sym1.equal?(sym2)}"
puts "sym2.equal?(sym3): #{sym2.equal?(sym3)}"

Running this shows:

=== Using the symbol :Fred in different contexts ===

In module One, using symbol :Fred to get the class
const_get(:Fred) returns: Class
Creating instance: Hello from Fred the class!
Returned symbol object_id: 18152204

In module Two, using symbol :Fred to get the constant
const_get(:Fred) returns: Integer with value 42
Calculating 42 * 2 = 84
Returned symbol object_id: 18152204

In module Three, using symbol :Fred to call the method
send(:Fred) returns: I'm the Fred method!
Returned symbol object_id: 18152204

=== The symbol :Fred is the same everywhere ===
All three object_ids match: true

What is even going on here? :Fred has the same object_id but when we consume it, it calls different things. I originally assumed :Fred was like atoms in Erlang, which I have a bare understanding of. But that doesn’t seem to be the case.

To reiterate if you haven’t understood, :Fred is a Symbol object. It doesn’t directly reference the actual method, class or variable that we are seeing in these code snippets, but it’s pointing to the name of these objects itself. In a real-world setting, you could conceptualize :Fred as a name tag itself with “Fred” written on it. In one situation, Fred could be the key in a dictionary that you could look up. In another it could be a name in a contacts app, or a name in a class roster at university. The symbol :Fred is the word itself, not what it points to.

It’s Ruby’s way of turning identifiers into first-class objects. In the loops post, I’ve written about .send(:times). Ruby makes this possible because :times is the method name as an object.

In Python, you’d need to use strings for this purpose. There’s no sense of meaning in the string other than the context that tells someone reading your code that you’re trying to get a method or attribute. And even that is a frail way of expressing intent in my opinion.

PythonRuby
getattr(obj, "method_name")obj.send(:method_name)
"method_name" is a string - could be data or an identifier:method_name is a symbol - explicitly an identifier

I’ve written before about Ruby’s preference for protocol over syntax. This falls perfectly in line with that.

I want to understand symbols a lot better though. What drove this design choice?

Smalltalk method names are symbols, message dispatch is a symbol-based lookup. That’s yet another thing Ruby inherited from it. Lisp/Scheme have symbols as first-class citizens. All function names are symbols. Clojure has :keyword style symbols just like Ruby. The dispatch mechanics are also very similar.

When I first started learning Ruby last month, I looked at the :symbol syntax and my brain immediately thought of Erlang’s atoms. Erlang calls them atom, and Elixir (which runs on the Erlang VM) uses the more familiar :atom syntax.

Erlang though, has atoms rooted even more deeply into its syntax. Consider the following Elixir code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18

# booleans are atoms
true # actually :true
false # actually :false

# return valules are tagged with atoms
{:ok, result}
{:error, result}

# Pattern matching uses atoms as tags
case File.read("data.txt") do
  {:ok, contents} -> process(contents)
  {:error, reason} -> handle_error(reason)
end

# module and function names are atoms

:lists.map(fn x -> x * 2 end, [1, 2, 3])

Erlang’s atoms are designed for its actor model. They are used to tag messages and data structures for pattern matching in concurrent systems. Using :ok or :error isn’t just convention, it’s how Erlang allows communication between processes.

Ruby’s symbols are designed for metaprogramming. With symbols, identifiers become objects you can manipulate at runtime. send(:method_name) or define_method(:foo) would not work without symbols as first-class citizens.

Interestingly, Both languages share a historical bug: creating symbols/atoms dynamically from user input could exhaust memory, since the symbol/atom table was never garbage collected. Erlang still warns about this in its efficiency guide, as atoms remain permanent for the VM’s lifetime. Ruby fixed this in version 2.2 by adding garbage collection for dynamically created symbols.

I think the first place you’d see symbols in Ruby are in hashmaps.

1
2
user = { "name" => "Alice", "age" => 30 }  # String keys
user = { :name => "Alice", :age => 30 }    # Symbol keys

These are two ways to create hash objects with different key types. But when you’re writing software that indicates intent, there’s a glaring difference between the two.

:name is a stronger indication of what you want to do than 'name'.

Modern ruby forgoes the arrow syntax to allow you to use symbols like this.

1
user = { name: "Alice", age: 30 }

That uses symbols for :name and :age internally.

Symbols aren’t interchangeable though.

1
2
3
user = { name: "Alice", age: 30 }
puts user[:name] # Alice
puts user["name"] # nil

But where they’re really powerful is when you reuse the keys.

1
2
1000.times { { "name" => "Alice" } }  # Creates 1000 "name" strings
1000.times { { name: "Alice" } }      # Uses the same :name symbol

Remember what I said about the symbol reusing the same memory space? The second example uses only one instance of :name, while the first one creates "name" 1000 times.

Metaprogramming 101

Another example of how symbols empower Ruby is with the helper methods I use within a class.

1
2
3
class Person
  attr_accessor :name
end

This is equivalent of writing:

1
2
3
4
5
6
7
8
9
class Person
  def name
    @name
  end

  def name=(value)
    @name = value
  end
end

In this example, Ruby uses the symbol :name to dynamically create getter and setter methods at runtime.

attr_accessor is a class method that takes a symbol and uses metaprogramming to define methods.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class Module
  def attr_accessor(name) # name is a symbol here
    # define getter method
    define_method(name) do
      instance_variable_get("@#{name}")
    end

    # define setter method
    define_method("#{name}=") do |value|
      instance_variable_set("@#{name}", value)
    end
  end
end

I’m hand-waving a lot of the details there, but this is a very clean example of how powerful symbols are.

While it is easy to think of attr_accessor as special syntax, it is just a method that takes symbols as arguments and writes code at runtime.

Fun with Symbols

I felt I still didn’t understand the power of symbols over strings and accessing attributes with them so I spun up irb and decided to write some Ruby.

Symbols are callable

I’ve seen this before:

I didn’t think much of it, but now I understand that this calls :to_s on these items. That’s cool, but what else can I use this way?

1
2
add_one = :+.to_proc
add_one.call(5,1) # 6

I knew that operators are methods, we can do 5.+(3) instead of 5 + 3, which is why this works. I understood that but seeing it like this… is so cool!

The & operator calls .to_proc on the symbol, which returns a proc that calls the method: { |obj, arg| obj.send(:+, arg) }

Am I rambling? I’m certain I am, but let’s go on.

Can I chain symbols?

I’ve done stuff like this:

1
["hello", "world"].map(&:upcase).map(&:reverse)

But we can also do this.

1
["hello", "world"].map { |s| s.upcase.reverse }

Then does that mean we can do this?

1
2
3

users.map(&:name).map(&:upcase) # This works
users.map { |u| u.name.upcase } # This is more efficient though

It’s always nice to ask ourselves if we should, just because we could.

More Operator Foo

1
2
3
4

5.send(:+, 3)
10.send(:*, 2)
[1,2].send(:<<, 3)

But wait…

1
2
operators = [:+, :-, :*, :/]
operators.map { |op| 10.send(op, 2)}

Well, if I see code like that in a PR, I’m going to call DHH personally. I don’t have his number though.

Wait, where are the enums?

I have been wondering, where are the Enums in Ruby? Rust loves Enums, and any good python code needs to leverage it.

I think I’ve seen enums in Rails, and yes I know I haven’t touched that yet in my posts but I’m going to, soon.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# without rails

status = :pending # or :active, or :completed

# In Rails

class User < ApplicationRecord
  enum status: [:pending, :active, :suspended, :deleted]
end

user.status # :pending
user.pending? # true
user.active! # Changes status to active

I just had to check the Rails source code to see if it was actually a new type or…

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# File activerecord/lib/active_record/enum.rb, line 216
def enum(name = nil, values = nil, **options)
  if name
    values, options = options, {} unless values
    return _enum(name, values, **options)
  end

  definitions = options.slice!(:_prefix, :_suffix, :_scopes, :_default, :_instance_methods)
  options.transform_keys! { |key| :"#{key[1..-1]}" }

  definitions.each { |name, values| _enum(name, values, **options) }

  ActiveRecord.deprecator.warn(<<~MSG)
    Defining enums with keyword arguments is deprecated and will be removed
    in Rails 8.0. Positional arguments should be used instead:

    #{definitions.map { |name, values| "enum :#{name}, #{values}" }.join("\n")}
  MSG
end

Dang it, Ruby. You never cease to amaze me!

That’s a method! This is so cool. I was just thinking how cool it would be to define an Enum in ruby so that I could give it a list of accepted values as symbols and it would then say something is not a valid option, and this exists. So cool!

Symbols and what they mean

You don’t have to be Robert Langdon to appreciate symbols in Ruby. I could spend so much more time on them, writing about Symbols.all_symbols (Ruby’s internal symbol table), method introspection (with method(), .respond_to?), and runtime reflection. I am sure I’ll have more to say about symbols in the future, once I start writing about metaprogramming. Can you tell I’m excited about that post already?