ruby/ruby
String and Symbol
String is the most heavily-used heap type in Ruby. Symbol is its frozen, interned cousin. Both are encoding-aware (see systems/encoding.md).
Files
| File | Purpose |
|---|---|
string.c |
The String C implementation. ~359 KB. |
string.rb |
Some String methods in Ruby. |
symbol.c |
Symbol and the global symbol table. |
symbol.rb |
Some Symbol methods in Ruby. |
symbol.h |
Symbol IDs and helpers. |
include/ruby/internal/core/rstring.h |
RString struct layout. |
String memory layout
struct RString {
struct RBasic basic;
union {
struct {
long len; /* byte length */
char *ptr;
union {
long capa;
VALUE shared; /* if shared with another String */
} aux;
} heap;
struct {
char ary[RSTRING_EMBED_LEN_MAX + 1]; /* embedded short string */
} embed;
} as;
};A String is one of three flavors:
- Embedded: short strings (up to ~24 bytes on 64-bit) live inline in the
RString. No separate heap allocation. - Heap-allocated: longer strings have
aux.capaand a separateptrmalloc. - Shared: a substring or
String#dupthat copy-on-write shares the underlying buffer with another String.aux.sharedpoints to the parent.
The flags in basic.flags encode the flavor and the encoding index. RSTRING_LEN(s) and RSTRING_PTR(s) return the length in bytes and the data pointer regardless of flavor.
Mutability and freezing
Strings are mutable by default. Each mutation may:
- Trigger a copy-on-write if the string is shared.
- Grow the buffer (
STR_BUF_MIN_SIZEis 127 bytes; growth is rounded up to the next size class). - Update the byte length and possibly invalidate cached character length / coderange.
Frozen strings raise FrozenError on mutation. With # frozen_string_literal: true (or default in modern Ruby), all string literals are frozen at parse time.
The frozen-string optimisation: the parser deduplicates frozen literals — "foo" (frozen) appearing twice in a file uses the same RString. See string.c::rb_fstring.
Encoding handling
Every String has an Encoding (see systems/encoding.md). The encoding is stored as an integer index in the flags. Methods that interpret characters (length, each_char, chars, [] with a character index, =~) consult the encoding's character-stepping callback.
The "coderange" caches whether the string is ASCII-only, valid in its encoding, broken, or unknown. It's stored in the flags and updated lazily.
Selected method implementations
| Method | Where |
|---|---|
String#length / #size |
string.c::rb_str_strlen (encoding-aware) |
String#bytesize |
string.c::rb_str_bytesize (returns RSTRING_LEN) |
String#+ |
string.c::rb_str_plus (with encoding-compatibility check) |
String#<< (concat) |
string.c::rb_str_concat |
String#sub / #gsub |
re.c (delegates to the regex engine) |
String#split |
string.c::rb_str_split_m |
String#[] |
string.c::rb_str_aref (handles Integer, Range, Regexp, String args) |
String#each_char |
string.c::rb_str_each_char |
String#encode |
transcode.c |
String#hash |
string.c::rb_str_hash (uses SipHash) |
String#unpack |
pack.c |
String#scan |
re.c::rb_str_scan |
Performance optimisations
- String comparison fast path:
==first compares lengths, then compares pointers (for shared strings), then memcmp. - Frozen literal interning: at parse time, frozen literals share storage.
"foo".equal?("foo")istruein a# frozen_string_literal: truefile. - Inline cache for
String#+@/String#-@: the-@operator returns a frozen interned copy. - Substring sharing:
s[5, 10]returns a String that shares the parent's buffer until either is mutated. - Hash caching: a string's hash is cached after the first computation if the string is frozen.
The String/Symbol divide
A Symbol is essentially "an interned, frozen String identifier". Symbols are deduplicated in a global table (symbol.c::global_symbols):
:foo.equal?(:foo) # true: same VALUE
"foo".equal?("foo") # depends on frozen-string-literalInternally, every Symbol has an integer ID (ID in C). IDs are reused across the symbol table; the mapping is id ↔ Symbol object ↔ String text.
There are two kinds:
- Static symbols: short ASCII-only symbols are encoded directly in the
VALUEbits — no allocation.:foo,:bar,:method_name. - Dynamic symbols: longer or non-ASCII symbols are heap objects that carry a String. These are GC-able since Ruby 2.2 (the "Symbol GC" feature).
symbol.c handles both flavors and the transition between them.
ID — the underlying integer
An ID is a unique integer for each known identifier. Methods, variables, and constants are looked up by ID, not by string. Conversions:
rb_intern("foo")→IDrb_id2sym(id)→VALUE(a Symbol)rb_sym2id(sym)→ID
rb_intern_const("foo") is a compile-time-friendly variant for static interning.
symbol.h and symbol.c define IDs in classes (e.g., method names) and IDs in the global table (e.g., user-supplied symbols).
Mutability landmines
String#freezereturns the same String: it's an in-place change.String#dupalways returns a mutable copy even if the source is frozen.String#clonepreserves frozen-ness (matchesObject#clonesemantics).String#+@returnsselfif mutable, a mutable dup if frozen.String#-@returns the frozen-interned canonical form.
Common pitfalls
- Frozen-string-literal pragma: opt-in via
# frozen_string_literal: true. Without it, all"..."literals are mutable. This pragma is becoming the default in newer Rubies. - Encoding mismatch:
"a" + japanese_stringworks only if both are ASCII-compatible and at least one is ASCII-only. - Symbol → String → Symbol round trip: cheap but allocates a String.
Symbol#to_sreturns a frozen string. String#hash/Symbol#hashare different even for equal text content. Use the type that matches your hash key.
Entry points for modification
- New String method: add to
string.c, register inInit_String. Add a Ruby-level wrapper instring.rbif it can be expressed in Ruby. - Encoding-aware operation: use
rb_enc_strlen,rb_enc_codepoint_len,rb_enc_codelen. AvoidRSTRING_LENfor character counts unless ASCII-only. - Performance: profile under YJIT (
--yjit-statsfor ICs). Hot paths usually benefit fromRB_FL_TESTchecks before calling encoding-aware variants. - Symbol behaviour:
symbol.c::global_symbolsis the central registry. Walkid_str_tablefor diagnostics.
See systems/encoding.md and systems/regexp.md for what String operations interact with.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.