ruby/ruby
Encoding (M17N)
Ruby's strings are encoding-aware: every String carries an Encoding, and operations like length, chars, regex matching, and case conversion respect that encoding. This is called M17N (multilingualisation, "M" + 17 letters + "N"). M17N landed in Ruby 1.9 and has been quietly central ever since.
Purpose
- Let Ruby strings represent text in any of ~100 supported encodings without forcing UTF-8 conversion.
- Provide explicit transcoding via
String#encode,IO#set_encoding, andEncoding::Converter. - Make encoding-aware operations (length in characters, regex matching, case folding, comparison) work correctly without programmer effort.
Files
| File | Purpose |
|---|---|
encoding.c |
The Encoding class, encoding registry, ASCII-compat helpers. ~52 KB. |
transcode.c |
String#encode and Encoding::Converter. ~145 KB. |
enc/ |
Per-encoding tables and methods. |
enc/encdb.c |
The encoding database. |
enc/<name>.c |
Code for each encoding (utf_8.c, shift_jis.c, euc_jp.c, ...). |
enc/trans/ |
Transcoding tables (Shift-JIS ↔ UTF-8, etc.). |
enc/unicode/data/ |
Unicode Character Database tables (case folding, normalization). |
encindex.h |
Compile-time index numbers for built-in encodings. |
tool/transcode-tblgen.rb |
Generates enc/trans/*.c from .trans source. |
tool/enc-unicode.rb |
Generates the Unicode tables in enc/unicode/data/. |
Encodings as objects
Every encoding is a singleton instance of Encoding:
Encoding::UTF_8 # frozen, single instance per encoding
Encoding::Shift_JIS
Encoding::ASCII_8BIT # the binary / "no encoding" sentinelInternally each encoding is an OnigEncodingTypeST (the regex engine's encoding type) extended with extra fields:
struct rb_encoding {
OnigEncodingType *base;
rb_encoding **prev_official_encoding;
/* + flags: ASCII-compat, dummy, fixed-width, ... */
};The encoding's "ASCII-compatible" flag means bytes 0x00–0x7F are valid one-byte characters and have ASCII meaning. Most encodings have it; UTF-16/UTF-32 don't.
A String stores its encoding as an integer index into a global encoding table (encoding.c::enc_table). RSTRING_LEN(s) gives bytes; rb_str_strlen(s) (or the Ruby-level String#length) decodes characters.
ASCII-only flag
A string can carry an "ASCII-only" flag separate from its encoding:
- Source: any string whose bytes are all <= 0x7F.
- Behaviour: treated as compatible with any ASCII-compatible encoding.
This lets "hello" + japanese_string work without manual coercion: the literal is ASCII-only, so it inherits the encoding of the right operand.
Encoding compatibility
encoding.c::enc_compatible_latter decides what encoding two strings produce when concatenated:
graph LR
A[Both same encoding] --> A1[Use it]
B[ASCII-only + ASCII-compat] --> B1[Use the ASCII-compat one]
C[ASCII-8BIT + ASCII-only] --> C1[Use the ASCII-compat one]
D[Otherwise] --> D1[Encoding::CompatibilityError]Encoding.compatible?(a, b) exposes this from Ruby.
Transcoding
String#encode(target) uses Encoding::Converter (transcode.c):
- For pairs that have a direct conversion table (Shift-JIS → UTF-8), one lookup per character.
- For pairs without a direct table, an intermediate UTF-8 hop ("Universal Newline" mode also routes through this).
- Options:
:invalid => :replace,:undef => :replace,:replace => '?',:newline => :universal, etc.
Encoding::Converter is a streaming API:
ec = Encoding::Converter.new("Shift_JIS", "UTF-8")
src = +"...".force_encoding("Shift_JIS")
dst = +""
ec.primitive_convert(src, dst)The converter holds a state machine; primitive_convert returns one of :source_buffer_empty, :destination_buffer_full, :incomplete_input, :undefined_conversion, :invalid_byte_sequence, :finished.
The conversion graph is described declaratively; transcode.c::transcode_search_path performs Dijkstra over the graph to find the shortest conversion sequence between two arbitrary encodings.
Built-in encodings
The first ~few dozen encodings are statically compiled in (encindex.h):
enum ruby_preserved_encindex {
RUBY_ENCINDEX_ASCII_8BIT = 0,
RUBY_ENCINDEX_UTF_8,
RUBY_ENCINDEX_US_ASCII,
/* + ~30 more for common European/Asian encodings */
};These are guaranteed to be present and have stable index numbers (used by C code as a fast path).
The rest of the encodings are loaded on demand via Encoding.find("name"), which dynamically opens enc/<name>.so.
Magic comments
A Ruby file's encoding can be set per-file with a magic comment on the first or second line:
# encoding: UTF-8
# frozen_string_literal: trueparse.y and prism/ both consume these. The default since 2.0 is UTF-8.
Default external/internal encodings
Encoding.default_external # encoding for IO from disk/network
Encoding.default_internal # if set, IO transcodes to this encodingSet at startup from LANG/LC_* (Linux), chcp (Windows), or via -E / --external-encoding=/--internal-encoding= flags. The defaults change the behaviour of File.read, ARGV, ENV, etc.
Onigmo integration
The regex engine (Onigmo, see regexp.md) is encoding-aware: every Regexp carries an encoding, and matching uses the engine's per-encoding character class tables. enc/<name>.c provides those tables (Unicode case-folding, character categorization, etc.) for each supported encoding.
Common pitfalls
- Mixing UTF-8 and binary: appending a UTF-8 String to an ASCII-8BIT String raises
Encoding::CompatibilityErrorif either has non-ASCII bytes. Useforce_encodingcarefully. force_encodingis a no-op tag change: it changes the encoding label without converting bytes. To actually convert, useencode.String#bytesvsString#chars: bytes are always raw; chars are encoding-decoded.- Source file encoding mismatch: a UTF-8 source file with an
# encoding: shift_jismagic comment will mis-decode literals.
Entry points for modification
- Add a new encoding: drop a new file under
enc/, register it inenc/encdb.c, and add transcoding tables underenc/trans/. Run the build (make srcs). - Fix a string operation: most string-encoding code is in
string.candencoding.c. The pattern is to switch onrb_enc_asciicompat(enc)andrb_enc_str_asciionly_p(s). - Update Unicode: bump the Unicode version in
tool/enc-unicode.rb's URL list, runmake update-unicode, regenerateenc/unicode/data/.
See systems/regexp.md for the regex engine and core-classes/string-and-symbol.md for String itself.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.