Ruby Command Line and Environment
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The reference material consulted while preparing these pages is listed in the section bibliography. |
This page is written against Ruby 4.0 (4.0.7, checked on 2026-10-18); features newer than Ruby 3.0 name the version that introduced them.
The ruby command is the front door of every Ruby program: it decides where the program text comes from, which libraries are
loaded before it starts, which warnings are shown, which encodings are the default, which parser and which JIT are used, and
how a failure is reported. Almost all of that is controlled by command-line options, and a second layer of it by environment
variables, which is what lets a deployment tune an application without editing its code. This page walks through the whole
surface: the synopsis and the ways of supplying a program, every option of the 4.0 ruby command grouped by purpose, the
text-processing one-liners that grew out of Ruby’s Perl heritage (-n, -p, -a, -F, -l, -i, -0), loading code with
-I and -r, warnings and debugging switches, the --enable/--disable feature flags, the parser and the --dump options,
JIT switches, and the environment variables (RUBYOPT, RUBYLIB, the RUBY_* runtime tuning variables, the garbage-collector
family and the RubyGems and Bundler variables).
The command line is the tool that runs everything else in this section. Installing Ruby and the first programs are in
Getting Started; the version policy is in
Versions, Releases and Compatibility. How require
searches $LOAD_PATH is in Code Loading: require and autoload;
gems and Bundler (which set RUBYOPT and GEM_HOME for you) are in
RubyGems and Bundler. Writing a program that has its own options
(OptionParser, ARGV, exit statuses) is in Command-Line Programs;
the interactive console and the debugger are in IRB and Debugging. The JIT
compilers and the virtual machine behind --dump are in YJIT, ZJIT and the VM,
and the garbage collector behind the RUBY_GC_* variables is in
Garbage Collection and Memory. For the equivalent in other
languages see Python getting started (the python command and PYTHONPATH),
Go getting started and
Rust getting started.
Every command on this page was run on Ruby 4.0.7 (macOS, arm64) with LANG=en_US.UTF-8; the Ruby files were syntax-checked
with ruby -wc, run, and linted with RuboCop using the configuration of the running shelf gem (target Ruby 4.0, double-quoted
strings). The output shown is the real output; numbers that depend on the machine (stack depth, memory limits, the number of
load-path entries) are marked. Version attributions were checked by running the same commands on Ruby 3.2, 3.3 and 3.4 where an
observable difference exists, and the ones taken from the manual page or the NEWS files without a run are said so. The shell
examples use `$ ` only where the command and its output need separating; the data file for the text-processing examples is a
small CSV of the shelf catalogue:
title,author,year
Picnic,A. Writer,2019
Ruby Basics,B. Coder,2021
Plain Text,A. Writer,2020
The synopsis of the ruby command
The manual page and ruby --help give the synopsis:
ruby [options] [--] [filepath] [arguments]
Everything before the program is an option of the interpreter; everything after it is an argument of the program and ends up
in ARGV. Two details cause most of the confusion:
-
Options are only recognised before the program name.
ruby -w script.rb -wrunsscript.rbin warning mode and gives itARGVof["-w"]. The--option terminator ends interpreter options explicitly, which matters when the program name itself could be mistaken for an option. -
With
-ethere is no program file, so every non-option word is an argument:ruby -e 'p ARGV' extra.rbprints["extra.rb"]and never runsextra.rb.
Where the program comes from
| Form | Program text | $0 and $PROGRAM_NAME |
|---|---|---|
|
The file |
|
|
The |
|
|
Standard input, read to the end before running. |
|
|
The part of a non-Ruby text file that starts at the first |
The file name. |
|
The first file called |
The resolved path. |
|
No program of its own; the point is the library loaded before it. |
|
Reading the whole program before running it matters for a point the older books get wrong. A Ruby file is not "parsed and
executed line by line": the complete file is parsed first (by Prism since 3.4), compiled to YARV bytecode, and only then run. That
is why a syntax error anywhere in a file stops the program before its first line runs, and why ruby -c can check a file
without executing it. The parser and the bytecode are shown later in this page with --dump.
When the file does not exist, or the code does not compile, the interpreter exits with status 1:
$ ruby nofile.rb
ruby: No such file or directory -- nofile.rb (LoadError)
$ ruby -c bad.rb
ruby: bad.rb:1: syntax errors found (SyntaxError)
> 1 | puts (1
| ^ unexpected end-of-input, assuming it is closing the parent top level context
> 2 |
| ^ expected a matching `)`
$ echo "rc=$?"
rc=1
The exit status is the program’s result for the shell: 0 for success, the number given to exit (ruby -e 'exit 3' ends with
status 3), and 1 for an uncaught exception, a syntax error or a missing file. Exit statuses are covered in
Command-Line Programs.
Identifying the interpreter
ruby -v prints the version and sets $VERBOSE (see below); ruby --version prints the same line and exits, and --copyright
prints the copyright line. The line is built from constants that a program can read too:
$ ruby --version
ruby 4.0.7 (2026-09-15 revision 229531a6cf) +PRISM [arm64-darwin23]
$ ruby --copyright
ruby - Copyright (C) 1993-2026 Yukihiro Matsumoto
$ ruby -e 'p RUBY_VERSION, RUBY_ENGINE, RUBY_PLATFORM, RUBY_RELEASE_DATE, RUBY_PATCHLEVEL'
"4.0.7"
"ruby"
"arm64-darwin23"
"2026-09-15"
0
The tokens at the end of the version line describe the build: +PRISM (the default parser since Ruby 3.4) appears unless the
parser was changed, +YJIT appears once YJIT is enabled, and the bracketed part is the platform. RUBY_ENGINE distinguishes CRuby
("ruby") from JRuby and TruffleRuby, and RbConfig::CONFIG (require "rbconfig") exposes the build configuration, for example
RbConfig.ruby for the path of the running interpreter, which is what a program should use to start another Ruby of the same
version.
A runtime report
The following script prints how the interpreter that runs it was started. It is used below to show the effect of options and environment variables:
# frozen_string_literal: true
# Prints how this interpreter was started.
categories = Warning.categories.to_h { |name| [name, Warning[name]] }
puts "ruby #{RUBY_DESCRIPTION}"
puts "program #{$PROGRAM_NAME}"
puts "arguments #{ARGV.inspect}"
puts "verbose #{$VERBOSE.inspect}, debug #{$DEBUG}"
puts "warnings #{categories}"
puts "encodings #{Encoding.default_external} / #{Encoding.default_internal.inspect}"
puts "yjit #{defined?(RubyVM::YJIT) ? RubyVM::YJIT.enabled? : 'not built'}"
puts "load path #{$LOAD_PATH.size} entries"
Source: Ruby 4.0 documentation: Warning
Run it plainly, then with several options at once (Warning.categories is Ruby 3.4+):
$ ruby runtime_report.rb a b
ruby ruby 4.0.7 (2026-09-15 revision 229531a6cf) +PRISM [arm64-darwin23]
program runtime_report.rb
arguments ["a", "b"]
verbose false, debug false
warnings {deprecated: false, experimental: true, performance: false, strict_unused_block: false}
encodings UTF-8 / nil
yjit false
load path 8 entries
$ ruby -w --yjit -E ISO-8859-1:UTF-8 -Ishelf runtime_report.rb
ruby ruby 4.0.7 (2026-09-15 revision 229531a6cf) +YJIT +PRISM [arm64-darwin23]
program runtime_report.rb
arguments []
verbose true, debug false
warnings {deprecated: true, experimental: true, performance: false, strict_unused_block: false}
encodings ISO-8859-1 / #<Encoding:UTF-8>
yjit true
load path 9 entries
The second run shows four independent effects: -w turned $VERBOSE on and enabled the deprecated warning category, --yjit
added +YJIT and made RubyVM::YJIT.enabled? true, -E replaced the two default encodings, and -I added a ninth
$LOAD_PATH entry. The load-path count depends on the installation.
Options by purpose
The 4.0 ruby --help lists the options below. The tables group them by what they control; the sections after them show the ones
that need an example. An option with an argument usually takes it attached (-Ilib, -rjson) or separated by a space
(-I lib, -r json); the exceptions are marked.
Choosing and checking the program
| Option | Meaning |
|---|---|
|
Execute the given code; may be repeated, the pieces are joined by newlines. Extra non-option words become |
|
Check the syntax and exit without running (prints |
|
Change to |
|
Run the Ruby program embedded in a text file: skip everything before the first line that starts with |
|
Look for the program name in |
|
Parse switches that follow the program name as global variables (see below). Not usable with |
|
Print a short or the long help text. The pager used for the long text is named by |
|
Print the version and set |
|
Print the version or the copyright line and exit. |
Text-processing loops
| Option | Meaning |
|---|---|
|
Wrap the program in |
|
Like |
|
With |
|
The field separator used by |
|
With |
|
Set the input record separator |
|
In-place editing for files read through |
Load path, libraries and the working directory
| Option | Meaning |
|---|---|
|
Add a directory at the front of |
|
|
|
See above; relative |
Warnings and diagnostics
| Option | Meaning |
|---|---|
|
Turn warnings on: |
|
Set the warning level ( |
|
Set |
|
Set |
|
Show at most |
|
Path template for the crash report file (same as the |
Encodings
| Option | Meaning |
|---|---|
|
Set |
|
Set only one of the two defaults. |
|
Set |
|
Legacy: set the source and default external encoding by a Japanese-encoding letter ( |
Features, parser and VM
| Option | Meaning |
|---|---|
|
Turn a named interpreter feature on or off; |
|
Choose the parser (Ruby 3.3 and later; Prism is the default from 3.4). |
|
Print internal representations: |
|
Print the parser’s trace (with |
|
Enable the YJIT compiler and tune it ( |
|
Enable the experimental ZJIT compiler when the build includes it. |
-T (taint checking) and $SAFE were removed with the taint system: on Ruby 3.2, 3.3, 3.4 and 4.0, ruby -T -e 1 fails with
invalid option -T. --rjit was removed in 4.0 (invalid option --rjit), and MJIT before it. The manual page installed with
4.0.7 still mentions -T, and also describes -w as "level 1" like the options guide does; the observed behaviour of both is
described in the sections below, and the observed behaviour is what this page documents.
Running code
One expression: -e
$ ruby -e 'puts "Hello, shelf"'
Hello, shelf
$ ruby -e 'puts "a"' -e 'puts "b"'
a
b
$ ruby -rjson -e 'puts JSON.generate(title: "Picnic", year: 2019)'
{"title":"Picnic","year":2019}
Shell quoting is the usual trap: wrap the code in single quotes so that $_, #{...} and ! reach Ruby untouched. A program
that must contain a single quote is better put in a file, or written with %q and %Q literals.
Standard input
A program can be piped in. With no file, ruby reads standard input to the end and then runs it; - makes that explicit and
lets arguments follow:
$ echo 'puts 6 * 7' | ruby
42
$ echo 'puts ARGV.inspect' | ruby - x y
["x", "y"]
--verbose and -v without a program do not read standard input: they print (or set) and exit, so echo 'p 1' | ruby -v shows
only the version. -w and -W do read it.
Syntax checking: -c and -wc
ruby -c file.rb parses without running. Adding -w includes the parse-time warnings, which is why this page’s examples are all
checked with ruby -wc:
$ cat w.rb
# frozen_string_literal: true
def title(book) = book.to_s
x = 1
puts title("a")
$ ruby w.rb
a
$ ruby -w w.rb
w.rb:4: warning: assigned but unused variable - x
a
$ ruby -wc w.rb
w.rb:4: warning: assigned but unused variable - x
Syntax OK
$ ruby -W0 w.rb
a
The warning appears at parse time, so -wc shows it without executing anything. ruby -wc is a cheap CI step and an editor
on-save check; static analysers that go much further are in
Style, Linting and Static Analysis.
Another directory: -C
-C changes directory before the program (and relative -I paths) is resolved, without changing the caller’s shell:
$ ruby -C cdir where.rb
cdir
$ ruby cdir/where.rb
w
$ ruby -C nosuchdir -e 1
ruby: Can't chdir to nosuchdir (fatal)
where where.rb prints Dir.pwd.split("/").last. In the first run the program’s working directory is cdir; in the second it is
the directory the shell was in (called w here).
Programs embedded in text: -x
-x skips leading text up to a #! line that mentions ruby. It is for programs shipped inside a mail, a shell script or a
document:
$ cat emb.txt
Leading text.
#!ruby
puts "from embedded: #{ARGV.inspect}"
__END__
trailing
$ ruby -x emb.txt a b
from embedded: ["a", "b"]
Scripts on the PATH: shebang, -S and env
A script is made executable with a shebang line and the executable bit. The portable form uses env so that the first ruby on
the PATH is chosen, which is what version managers (mise, rbenv, asdf, chruby) rely on:
#!/usr/bin/env ruby
# frozen_string_literal: true
# A script found through PATH when started as: ruby -S shelf-tool
puts "#{File.basename($PROGRAM_NAME)} started with #{ARGV.inspect}"
$ chmod +x shelf-tool
$ ./shelf-tool x
shelf-tool started with ["x"]
$ ruby shelf-tool y
shelf-tool started with ["y"]
$ PATH="$PWD/bin:$PATH" ruby -S shelf-tool a b
shelf-tool started with ["a", "b"]
$ RUBYPATH="$PWD/bin" ruby -S shelf-tool
shelf-tool started with []
-S with no match fails with No such file or directory — shelf-tool (LoadError). Directories in RUBYPATH are searched
before those in PATH.
Options can also be written on the shebang line itself: ruby scans the #! line of a script for options when the line contains
the word ruby, so a file that starts with #!/usr/bin/env ruby -w run through ruby file.rb or ./file.rb has warnings on
(verified on macOS). Operating systems differ in how a shebang with several words is split before it reaches env; the portable
spelling for passing options is #!/usr/bin/env -S ruby -w with GNU and BSD env -S. Prefer putting options a program needs
into the program (Warning[:deprecated] = true) or into RUBYOPT, rather than into the shebang.
Switches for the program: -s
With -s, switches written after the program name and before any argument are removed from ARGV and turned into global
variables: -term=Picnic sets $term to "Picnic", a bare -all sets $all to true, and dashes inside the name become
underscores (-foo-bar=1 sets $foo_bar). This is a feature for tiny scripts; anything larger should use OptionParser
(see Command-Line Programs) because -s has no validation, no help and
no --long options.
# frozen_string_literal: true
puts "term=#{$term.inspect} all=#{$all.inspect}" # rubocop:disable Style/GlobalVars
The rubocop:disable comment is deliberate: reading a user-defined global variable is exactly what the example is about, and the
default Style/GlobalVars cop forbids it.
$ ruby -s switches.rb -term=Picnic -all
term="Picnic" all=true
$ ruby -s switches.rb
term=nil all=nil
$ ruby -s switches.rb -- -term=x
term=nil all=nil
$ ruby switches.rb -term=x
term=nil all=nil
Three of these four runs leave the variables unset: -- ends switch parsing, and without -s the word is just an argument. A
switch placed after the first ordinary argument is not parsed either (ruby -s s2.rb file -a leaves $a unset).
Standard-library tools: -run
ruby -run -e name loads the standard un library and runs one of its small file commands, a cross-platform replacement for
cp, mv, rm, mkdir, touch, chmod, install, ln, rmdir, wait_writable, mkmf, colorize, httpd and help.
It is -r plus un, which is why the odd spelling works: -r followed by un.
$ ruby -run -e mkdir -- -p a/b/c
$ ruby -run -e touch -- a/b/c/x
$ ruby -run -e rm -- -r a
$ ruby -run -e help touch
Update the access and modification times of each FILE to the current time.
ruby -run -e touch -- [OPTION] FILE
-v verbose
The -- separates the ruby options from those of the un command. httpd needs the webrick gem, which has not been part
of Ruby since 3.0, so without that gem the command stops with a message that webrick is not found.
One-liners for text processing
The loop options come from Perl and sed. They are worth knowing because a one-liner is often the quickest report on a log or a
CSV file, and because the same ideas ($_, $., $F, ARGF) show up in scripts. The options expand like this:
-
-niswhile gets; ...; end, reading fromARGF(the files named after the program, or standard input). The current line is in$_, its number in$., and a bare regular expression orprintwith no argument uses$_. -
-pis-nthat prints$_at the end of every pass. -
-aadds$F = $_.split, using$;as the separator;-Fsets it. -
-lchops the newline off$_and makesprintandputsend lines with$\, so the output keeps its line structure. -
BEGIN { }andEND { }blocks run once, before the first and after the last line.
Filtering with -n
$ ruby -ne 'print if /Writer/' books.csv
Picnic,A. Writer,2019
Plain Text,A. Writer,2020
$ ruby -ne 'puts "#{$.}: #{$_}" if $. > 1' books.csv
2: Picnic,A. Writer,2019
3: Ruby Basics,B. Coder,2021
4: Plain Text,A. Writer,2020
$ ruby -ne 'BEGIN { n = 0 }; n += 1; END { puts n }' books.csv
4
$ ruby -ne 'print if 2..3' books.csv
Picnic,A. Writer,2019
Ruby Basics,B. Coder,2021
The last command uses a flip-flop: a range literal used as a condition is compared with the line number $., so 2..3 is true
for lines 2 and 3. The flip-flop is explained with the conditional forms in
Control Expressions. $_ keeps the newline unless -l is given, which
is why the first command prints no blank lines but p $_ shows "...\n".
Fields with -a, -F and -l
$ ruby -F, -lane 'puts $F[0] unless $. == 1' books.csv
Picnic
Ruby Basics
Plain Text
$ ruby -F, -lane 'BEGIN { total = 0 }; total += $F[2].to_i; END { puts total }' books.csv
6060
$ ruby -F, -ane 'next if $. == 1; puts $F.values_at(0, 2).join(" - ")' books.csv
Picnic - 2019
Ruby Basics - 2021
Plain Text - 2020
$ ruby -F'\t' -ane 'p $F' <<< $'a\tb c'
["a", "b c\n"]
The cluster -lane is four options in one word (-l -a -n -e), with -e last because it takes the program as its argument.
Note the total: the header’s "year".to_i is 0, so it does not disturb the sum, which is also why -F, is only a sketch of CSV
reading. Fields with quoted commas need the csv library (see
Serialization: JSON, YAML, CSV and Marshal).
In the last command -l was not given, so the final field still carries its newline.
Editing with -p and -i
-p is for filters that change each line. A next inside a -p program skips the print for that line, which makes next
a delete:
$ ruby -lpe '$_ = $_.upcase if $. == 1' books.csv
TITLE,AUTHOR,YEAR
Picnic,A. Writer,2019
Ruby Basics,B. Coder,2021
Plain Text,A. Writer,2020
$ ruby -pe 'next if $. == 1; $_.tr!(",", "\t")' books.csv
Picnic A. Writer 2019
Ruby Basics B. Coder 2021
Plain Text A. Writer 2020
The second command drops the header line (no print) and converts the commas of the others to tabs.
-i makes the output replace the input file. With an extension the original is kept; without, it is gone:
$ cp books.csv inplace.csv
$ ruby -i.bak -pe '$_.gsub!("A. Writer", "Ann Writer")' inplace.csv
$ cat inplace.csv
title,author,year
Picnic,Ann Writer,2019
Ruby Basics,B. Coder,2021
Plain Text,Ann Writer,2020
$ head -2 inplace.csv.bak
title,author,year
Picnic,A. Writer,2019
Source: Ruby 4.0 documentation: ARGF
Always give -i an extension while experimenting (ruby -i -pe ... has no way back), and try the command without -i first.
-i sets ARGF.inplace_mode, which is nil without the option, "" for a bare -i and the extension otherwise.
Record separators: -0
The input record separator $/ decides what gets (and therefore -n) calls a line:
$ ruby -0777 -ne 'p $_.bytesize, $/' books.csv
92
nil
$ ruby -00 -ne 'p $_' <<'EOF'
one
two
three
EOF
"one\ntwo\n\n"
"three\n"
$ printf 'a;b;' | ruby -0073 -ne 'p $_'
"a;"
"b;"
-0777 reads each file whole (slurp mode); -00 splits on blank lines (paragraph mode); -0073 uses the semicolon, character
code 073 in octal. A trap for the careful: String#lines and String#each_line also default to $/. After -0777, $_.lines
is a one-element array and $_.lines("\n") is the way to get the four lines; in paragraph mode the same happens with $/ set to
the empty string.
Other loop patterns
ARGF is the object behind gets and $<; a one-liner without -n can use it directly, which also gives access to
several files at once:
$ ruby -e 'puts ARGF.read.lines.size' books.csv books.csv
8
$ ruby -rjson -e 'puts JSON.generate(ARGF.each_line.drop(1).map { |l| t, _, y = l.chomp.split(","); { title: t, year: y.to_i } })' books.csv
[{"title":"Picnic","year":2019},{"title":"Ruby Basics","year":2021},{"title":"Plain Text","year":2020}]
Source: Ruby 4.0 documentation: ARGF
Both lines are short enough to be useful in a shell history and long enough that a script file is the better home once it grows;
ARGF, $stdin and the other process-level streams are developed in
Command-Line Programs and
IO, Files and Directories.
Loading code: -I, -r and RUBYLIB
-I dir adds a directory to the front of $LOAD_PATH, so that require "name" finds dir/name.rb; -r lib requires a
library before the program starts. Together they are the quickest way to try the shelf gem without installing it:
$ cd shelf
$ ruby -Ilib -rshelf -e 'puts Shelf::VERSION'
0.1.0
$ ruby -Ilib exe/shelf --search ruby
ruby basics by A. Writer
$ ruby -Ilib exe/shelf -v
0.1.0
$ ruby -Ilib -rshelf -e 'p Shelf::Isbn.new("978-0-306-40615-7")'
#<data Shelf::Isbn digits="9780306406157">
$ ruby -rshelf -e 1
<internal:.../rubygems/core_ext/kernel_require.rb>:139:in 'Kernel#require': cannot load such file -- shelf (LoadError)
The last command fails because the library directory is not on the load path: a gem works with require only once it is
installed (or loaded by Bundler, see below), and an uninstalled working copy needs -I. Relative -I directories are expanded
to absolute paths when the interpreter starts, so a later Dir.chdir does not break them, and that expansion happens relative
to the directory selected with -C.
A script can show where it got its load path:
# frozen_string_literal: true
puts $LOAD_PATH.first(3).map { |dir| File.basename(dir) }.inspect
require "shelf"
puts Shelf::VERSION
puts $LOADED_FEATURES.grep(/shelf/).size
$ ruby -Ilib load_path.rb
["lib", "4.0.0", "arm64-darwin23"]
0.1.0
9
$ ruby load_path.rb
load_path.rb:4:in 'Kernel#require': cannot load such file -- shelf (LoadError)
from load_path.rb:4:in '<main>'
["4.0.0", "arm64-darwin23", "site_ruby"]
$ RUBYLIB=lib ruby load_path.rb
["lib", "4.0.0", "arm64-darwin23"]
0.1.0
9
$ RUBYLIB=/x ruby -Ilib load_path.rb
["lib", "x", "4.0.0"]
0.1.0
9
(In the second run the error is printed to standard error, which is why the two lines appear in that order.) RUBYLIB is the
environment counterpart of -I: its directories are separated by : (; on Windows) and come after -I directories but before
the standard library. The $LOADED_FEATURES count is 9 because requiring shelf loads its eight files plus shelf.rb itself.
The search order, then, is: -I directories (in the order given), RUBYLIB, gem paths added by RubyGems at require time,
and the Ruby installation’s own directories (site_ruby, vendor_ruby, the standard library). require itself, require_relative,
load and autoload are in Code Loading: require and autoload.
Warnings and debugging
Levels: -w, -W and $VERBOSE
Ruby has three warning levels, stored in $VERBOSE (and as a number in $-W). The options set them at startup:
| Option | $VERBOSE |
$-W |
Effect |
|---|---|---|---|
|
|
0 |
Silent: even |
none, or |
|
1 |
Normal: the interpreter’s default warnings. |
|
|
2 |
Verbose: extra warnings (unused variables, ambiguous syntax, redefined methods) and the |
The table is what Ruby 4.0.7 does when asked, using ruby -W1 -e 'p [$VERBOSE, $-W]' and so on (for -w it prints
[true, 2], for -W1 [false, 1]); the same results come out of 3.2, 3.3 and 3.4. The options guide and ruby --help call
-w a "synonym for -W1`", which does not match: `-w is the verbose level, and -W1 is the default. -d additionally sets
$DEBUG.
$ for o in "" -w -W0 -W1 -W2 -W -v -d; do echo "[$o]"; ruby $o -e 'p [$VERBOSE, $-W, $DEBUG]' 2>&1 | tail -1; done
[]
[false, 1, false]
[-w]
[true, 2, false]
[-W0]
[nil, 0, false]
[-W1]
[false, 1, false]
[-W2]
[true, 2, false]
[-W]
[true, 2, false]
[-v]
[true, 2, false]
[-d]
[true, 2, true]
(The [true, 2, false] of -v is the last line of its output: -v prints the version line first.) Code can read and change
$VERBOSE at runtime ($VERBOSE = nil silences warnings inside a block of code, restored in an ensure), but a library should
not change a global that belongs to its caller. $DEBUG makes the interpreter print each exception as it is raised, including the
ones raised and rescued inside RubyGems during startup (a line starting Exception 'LoadError' at and ending with cannot load such file -- rubygems/defaults/operating_system), which is noisy but useful when something rescues too much.
Categories: -W:category
Individual categories can be switched with -W:name and -W:no-name, and from code with Warning[:name] = true.
Warning.categories lists them (Ruby 3.4+; on 3.3 the call is a NoMethodError):
| Category | Covers | Default |
|---|---|---|
|
Features slated for removal (for example, in 3.4, modifying a chilled string literal). |
off (on with |
|
Features that may change ( |
on |
|
Code that is slow for an avoidable reason. |
off |
|
A block passed to a method that never uses it (Ruby 3.4+; Ruby 3.2 and 3.3 report an unknown category). |
off |
Source: Ruby 4.0 documentation: Warning
An unknown category is itself a warning (ruby: warning: unknown warning category: 'foo'). -W0 turns every category off, but a
later -W:performance turns one back on; the order of options matters.
Kernel#warn takes a category: keyword, so a library can emit a message that follows the user’s choice, and an uplevel:
keyword to point at the caller’s line:
# frozen_string_literal: true
$stdout.sync = true # interleave stdout and stderr when both go to a pipe
warn "plain warnings always print (unless -W0)"
warn "shown only when :deprecated is on", category: :deprecated
warn "shown only when :experimental is on", category: :experimental
puts "deprecated=#{Warning[:deprecated]} experimental=#{Warning[:experimental]}"
Warning[:deprecated] = true
warn "now :deprecated is on", category: :deprecated
$ ruby warn_categories.rb 2>&1
plain warnings always print (unless -W0)
shown only when :experimental is on
deprecated=false experimental=true
now :deprecated is on
$ ruby -W:deprecated warn_categories.rb 2>&1
plain warnings always print (unless -W0)
shown only when :deprecated is on
shown only when :experimental is on
deprecated=true experimental=true
now :deprecated is on
$ ruby -W0 warn_categories.rb 2>&1
deprecated=false experimental=false
Source: Ruby 4.0 documentation: Warning
In the third run -W0 silenced both the plain warn and the categories, but the program’s own puts is, of course, unaffected.
Libraries that deprecate an API should call warn ..., category: :deprecated so that users see the notice when they run with
-w or -W:deprecated, and do not see it otherwise.
Two categories matter for the migration advice in this section:
-
Chilled string literals (Ruby 3.4): without a
frozen_string_literalmagic comment, a string literal is "chilled": it can still be modified, but doing so warns when thedeprecatedcategory is on. This is the step towards frozen literals that the Ruby team has taken instead of flipping the default (the default is still not frozen in 4.0):$ ruby -e 'a = "x"; a << "y"; p a' "xy" $ ruby -W:deprecated -e 'a = "x"; a << "y"' -e:1: warning: literal string will be frozen in the future (run with --debug-frozen-string-literal for more information)Ruby 3.3 has no warning here, and
ruby --dump=insnson 3.3 showsputstringwhere 3.4 and 4.0 showputchilledstring. -
Unused blocks (
strict_unused_block, 3.4):ruby -W:strict_unused_block -e 'def m = 1; m { }'printswarning: the block passed to 'Object#m' defined at -e:1 may be ignored.
Debugging aids
--backtrace-limit=n trims long backtraces, which is the setting for noisy frameworks; the omitted part is summarised:
$ cat bt.rb
def a = b
def b = c
def c = raise("boom")
a
$ ruby bt.rb
bt.rb:3:in 'Object#c': boom (RuntimeError)
from bt.rb:2:in 'Object#b'
from bt.rb:1:in 'Object#a'
from bt.rb:4:in '<main>'
$ ruby --backtrace-limit=1 bt.rb
bt.rb:3:in 'Object#c': boom (RuntimeError)
from bt.rb:2:in 'Object#b'
... 2 levels...
$ ruby --backtrace-limit=0 bt.rb
bt.rb:3:in 'Object#c': boom (RuntimeError)
... 3 levels...
The format of the lines ('Object#c' with a single quote and the class name) is the 3.4 style; older books show a backtick
before the method name and no class. The error message formats are in
Exceptions and Error Handling.
Three helper gems that ship with Ruby print better diagnostics, and each can be disabled with --disable:
$ cat eh.rb
book = nil
puts book.title
$ ruby eh.rb
eh.rb:2:in '<main>': undefined method 'title' for nil (NoMethodError)
puts book.title
^^^^^^
$ ruby --disable=error_highlight eh.rb
eh.rb:2:in '<main>': undefined method 'title' for nil (NoMethodError)
$ ruby -e 'prnt 1'
-e:1:in '<main>': undefined method 'prnt' for main (NoMethodError)
Did you mean? print
$ ruby --disable=did_you_mean -e 'prnt 1'
-e:1:in '<main>': undefined method 'prnt' for main (NoMethodError)
error_highlight underlines the call that failed (it needs a source file, so -e code gets no marker), did_you_mean suggests
names, and syntax_suggest points at the unbalanced end when a file does not parse:
$ cat sx.rb
class Loan
def due
[1, 2
end
end
$ ruby sx.rb
sx.rb: --> sx.rb
Unmatched `[', missing `]' ?
1 class Loan
2 def due
> 3 [1, 2
4 end
5 end
sx.rb:4: syntax errors found (SyntaxError)
2 | def due
3 | [1, 2
> 4 | end
| ^ expected an expression for the array element
| ^ unexpected 'end'; expected a `,` separator for the array elements
| ^~~ unexpected 'end'; expected a `]` to close the array
| ^~~ unexpected 'end', assuming it is closing the parent method definition
5 | end
$ ruby --disable=syntax_suggest sx.rb
sx.rb: sx.rb:4: syntax errors found (SyntaxError)
2 | def due
3 | [1, 2
> 4 | end
| ^ expected an expression for the array element
| ^ unexpected 'end'; expected a `,` separator for the array elements
| ^~~ unexpected 'end'; expected a `]` to close the array
| ^~~ unexpected 'end', assuming it is closing the parent method definition
5 | end
The interactive debugger and the debug gem are in
IRB and Debugging, and TracePoint and tracing in
Reflection, Introspection and Tracing.
Interpreter features: --enable and --disable
--enable=a,b and --disable=a,b switch named features of the interpreter; the hyphenated forms --enable-a and --disable-a
mean the same. The 4.0.7 ruby --help lists:
| Feature | What it controls | Default |
|---|---|---|
|
RubyGems. Disabling it removes |
enabled |
|
The marker under the failing expression in |
enabled |
|
"Did you mean?" suggestions. |
enabled |
|
The explanation for unbalanced |
enabled |
|
Reading the |
enabled |
|
Freeze all string literals of files that have no |
disabled |
|
The in-process JIT compiler (same as |
disabled |
--disable-all and --enable-all apply to every feature at once (with --disable-all, defined?(Gem) and
defined?(DidYouMean) are both nil). The 4.0 options guide lists a shorter set and names jit where --help names yjit
(--enable=jit also turns YJIT on); the table above is what the --help of the 4.0.7 build prints. Feature names keep their own
spelling: frozen-string-literal has hyphens and did_you_mean has underscores.
Frozen string literals
--enable=frozen-string-literal (or --enable-frozen-string-literal) freezes every string literal of the program, the switch
that lets a team try the future default:
$ cat f.rb
s = "shelf"
puts s.frozen?
begin
s << "!"
rescue FrozenError => e
puts e.message
end
$ ruby f.rb
false
$ ruby --enable-frozen-string-literal f.rb
true
can't modify frozen String: "shelf"
$ ruby --debug-frozen-string-literal -W:deprecated f.rb
f.rb:4: warning: literal string will be frozen in the future
f.rb:1: info: the string was created here
false
--debug-frozen-string-literal records where each literal was created, so that the error message of a FrozenError (or the
chilled-string warning above) names the creation site. The magic comment of a file wins over the switch, in both directions: a
file with # frozen_string_literal: false stays modifiable under --enable-frozen-string-literal, and one with
# frozen_string_literal: true stays frozen under --disable-frozen-string-literal.
# frozen_string_literal: false
title = "Picnic"
puts title.frozen?
$ ruby --enable=frozen-string-literal frozen_probe.rb
false
$ ruby --disable=frozen-string-literal frozen_probe2.rb
true
where frozen_probe2.rb is the same file with true. The strings page (Strings)
shows the idioms for building strings without relying on mutating a literal. Frozen literals are still not the default in 4.0,
even though some books say that 3.4 made them the default: 3.4 added the chilled-string warning, not the freeze.
Encodings: -E, -U and the locale
The default external encoding comes from the locale (LANG, LC_ALL, LC_CTYPE) and the default internal encoding is nil.
-E external:internal overrides them, -U sets the internal one to UTF-8:
$ ruby -e 'p [Encoding.default_external, Encoding.default_internal, __ENCODING__]'
[#<Encoding:UTF-8>, nil, #<Encoding:UTF-8>]
$ ruby -E ISO-8859-1:UTF-8 -e 'p [Encoding.default_external, Encoding.default_internal]'
[#<Encoding:ISO-8859-1>, #<Encoding:UTF-8>]
$ ruby -U -e 'p Encoding.default_internal'
#<Encoding:UTF-8>
$ LC_ALL=C ruby -e 'p Encoding.default_external'
#<Encoding:US-ASCII>
$ LC_ALL=C ruby -E UTF-8 -e 'p Encoding.default_external'
#<Encoding:UTF-8>
Source: Ruby 4.0 documentation: Encoding
__ENCODING__ is the source encoding, which is UTF-8 unless a # encoding: magic comment (or the legacy -K) says
otherwise; it does not follow the locale. A container or cron job with LANG unset starts with US-ASCII as the default external
encoding, which is a classic source of Encoding::UndefinedConversionError when reading UTF-8 files; setting LANG=C.UTF-8 or
passing -E UTF-8 fixes it. More in Encodings and Unicode.
The parser and the VM: --parser and --dump
Since Ruby 3.4 the default parser is Prism, a hand-written recursive-descent parser that produces an abstract syntax tree and
helpful syntax-error messages; the older parse.y (a Bison grammar) is still available. The version line shows which is
active (+PRISM), and --parser chooses (the option was added in 3.3, where Prism was experimental and printed a warning):
$ ruby --parser=prism -v
ruby 4.0.7 (2026-09-15 revision 229531a6cf) +PRISM [arm64-darwin23]
$ ruby --parser=parse.y -v
ruby 4.0.7 (2026-09-15 revision 229531a6cf) [arm64-darwin23]
$ ruby --parser=foo -e 1
ruby: unknown parser foo (RuntimeError)
For ordinary programs the two parsers behave the same; the choice matters for tool authors and for diagnosing a suspected parser
bug. RUBYOPT=--parser=parse.y also works. The Prism module (require "prism") exposes the same parser to Ruby code, which
is how RuboCop, Ruby LSP and other tools read source (see
Style, Linting and Static Analysis).
--dump
--dump=item prints an internal form and, for the dump items, does not run the program. The items are insns (YARV bytecode),
parsetree (the AST), yydebug (the parse.y parse trace); version, copyright, usage, help and syntax behave like
--version, --copyright, -h, --help and -c. Modifiers: -optimize (compile without optimisation), and
+error-tolerant (affects the yydebug and parsetree items) and +comment (adds comments to the AST of parse.y). Several items are separated by commas.
$ cat sum.rb
total = 1 + 2
$ ruby --dump=insns sum.rb
== disasm: #<ISeq:<main>@sum.rb:1 (1,0)-(1,13)>
local table (size: 1, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
[ 1] total@0
0000 putobject_INT2FIX_1_ ( 1)[Li]
0001 putobject 2
0003 opt_plus <calldata!mid:+, argc:1, ARGS_SIMPLE>[CcCr]
0005 dup
0006 setlocal_WC_0 total@0
0008 leave
$ ruby --dump=insns,-optimize sum.rb | tail -4
0004 send <calldata!mid:+, argc:1, ARGS_SIMPLE>, nil
0007 dup
0008 setlocal total@0, 0
0011 leave
$ ruby --dump=parsetree sum.rb | head -8
@ ProgramNode (location: (1,0)-(1,13))
+-- locals: [:total]
+-- statements:
@ StatementsNode (location: (1,0)-(1,13))
+-- body: (length: 1)
+-- @ LocalVariableWriteNode (location: (1,0)-(1,13))
+-- name: :total
+-- depth: 0
$ ruby --dump=insns -e 'puts "Picnic"'
== disasm: #<ISeq:<main>@-e:1 (1,0)-(1,13)>
0000 putself ( 1)[Li]
0001 putchilledstring "Picnic"
0003 opt_send_without_block <calldata!mid:puts, argc:1, FCALL|ARGS_SIMPLE>
0005 leave
Reading the first listing: 1 + 2 was compiled to the specialised instruction opt_plus, and with -optimize it becomes a
generic send of +. The putchilledstring in the last listing is the chilled literal from the previous section. The same
bytecode is available to a program as RubyVM::InstructionSequence:
# frozen_string_literal: true
iseq = RubyVM::InstructionSequence.compile("total = 1 + 2")
puts iseq.disasm
It prints the same disassembly as --dump=insns (with <compiled> in place of the file name). The AST dump under Prism is
Prism’s own text form; under --parser=parse.y it is the older NODE_* format, which begins with a banner saying it must
"not be used for any purpose other than debug and research": none of these formats is a stable interface. The VM, the
instruction set and the JIT that consumes the bytecode are in
YJIT, ZJIT and the VM.
ruby -y and --yydebug ask the parse.y parser for its trace; under the default Prism parser they print nothing, so add
--parser=parse.y (or use --dump=yydebug). Unknown dump items are reported and ignored:
ruby: warning: don't know how to dump 'foo', but only [version, copyright, usage, help, yydebug, syntax, parsetree, insns].
(note that prism_parsetree is not an item; parsetree prints the Prism tree on a Prism build).
JIT switches
Ruby 4.0 has two just-in-time compilers; neither is on by default.
| Switch | Meaning |
|---|---|
|
Compile hot Ruby methods to machine code with YJIT. |
|
Soft limit of YJIT memory (default 128). |
|
Hard limit of the executable memory block. |
|
Calls before a method is compiled (the YJIT guide says the default is 30, raised to 120 for very large programs). |
|
Calls after which a never-compiled method is considered cold (default 200000). |
|
Collect and print statistics; log compilation events. Both cost run time. |
|
Start with YJIT disabled but allow |
|
Discard code at the memory limit; frame pointers for Linux |
|
The experimental method-based compiler (Ruby 4.0). Available only in a build that includes ZJIT (needs Rust 1.85+ at build time). |
Source: Ruby 4.0 documentation: YJIT
YJIT is production-ready (since Ruby 3.2) and on a supported platform is the first thing to try for a CPU-bound web
application. Whether it is active is visible in three places, and ruby --yjit -v and ruby -v differ by +YJIT:
$ ruby -e 'p RubyVM::YJIT.enabled?'
false
$ ruby --yjit -e 'p RubyVM::YJIT.enabled?'
true
$ RUBY_YJIT_ENABLE=1 ruby -e 'p RubyVM::YJIT.enabled?'
true
$ ruby --yjit -v
ruby 4.0.7 (2026-09-15 revision 229531a6cf) +YJIT +PRISM [arm64-darwin23]
Source: Ruby 4.0 documentation: YJIT
Enabling YJIT after boot
Compiling during start-up wastes memory on code that runs once. RubyVM::YJIT.enable (Ruby 3.3) turns the compiler on from Ruby
code, typically after the application has loaded (or in a forked worker, after fork); the optional mem_size: and
call_threshold: keywords were added in Ruby 4.0 (on 3.3 and 3.4 passing them is an ArgumentError):
# frozen_string_literal: true
puts "before: #{RubyVM::YJIT.enabled?}"
RubyVM::YJIT.enable(mem_size: 64, call_threshold: 10)
puts "after: #{RubyVM::YJIT.enabled?}"
$ ruby yjit_enable.rb
before: false
after: true
$ ruby --yjit-disable yjit_enable.rb
before: false
after: true
$ ruby --yjit yjit_enable.rb
before: true
after: true
The second run is the useful one in practice: any --yjit-* tuning option otherwise starts YJIT at boot, and --yjit-disable
says "keep the tuning, delay the start".
ZJIT (experimental)
ZJIT was introduced in Ruby 4.0 as an experimental method-based compiler. The NEWS file for 4.0 says it is faster than the interpreter but not yet as fast as YJIT, and advises against production use; the stated goal is to make it faster than YJIT and production-ready in 4.1. ZJIT needs a Rust toolchain when Ruby is built; in a build without it (like the one used for this page) the option only warns:
$ ruby --zjit -e 'p 1'
ruby: warning: Ruby was built without ZJIT support. You may need to install rustc to build Ruby with ZJIT.
1
Source: Ruby 4.0 documentation: ZJIT
When it is available it has options of its own (--zjit-mem-size, --zjit-call-threshold, --zjit-num-profiles,
--zjit-stats, --zjit-disable, --zjit-perf, --zjit-log-compiled-iseqs, --zjit-trace-exits) and RubyVM::ZJIT.enable.
These are described in the ZJIT guide and were not run here. Treat ZJIT as something to experiment with, not to deploy.
Environment variables
An environment variable configures a process without touching its command line, which is what makes it the right tool for
deployment (container images, systemd units, CI) and for wrappers like Bundler. The set below is the one described by the
ruby(1) manual page and the options guides of 4.0; a variable that depends on a Ruby build or platform is marked.
RUBYOPT
RUBYOPT holds options that are added to the command line of every Ruby process that sees the variable, including the ones
started by a Ruby program, bundle exec, gem, rake and tests. That reach is both its use and its danger: a stray
RUBYOPT=-w in a shell profile floods the output of every tool with warnings from gems. Only options that do not choose the
program or change how it is read are accepted. Observed on 4.0.7:
$ RUBYOPT="-w" ruby w.rb
w.rb:4: warning: assigned but unused variable - x
a
$ RUBYOPT="-W:deprecated -rset" ruby -e 'p Warning[:deprecated]'
true
$ RUBYOPT="--enable-frozen-string-literal" ruby -e 'p "a".frozen?'
true
$ RUBYOPT="--yjit" ruby -e 'p RubyVM::YJIT.enabled?'
true
$ RUBYOPT="-Ishelf/lib -rshelf" ruby -e 'p Shelf::VERSION'
"0.1.0"
$ RUBYOPT="-e 'p 1'" ruby -e 1
ruby: invalid switch in RUBYOPT: -e (RuntimeError)
$ RUBYOPT="-n" ruby -e 1
ruby: invalid switch in RUBYOPT: -n (RuntimeError)
The rules, as observed:
-
Accepted:
-d,-E,-I,-r,-w,-W(with levels and:category),--backtrace-limit,--enable/--disableand their hyphen forms,--parserand--jit/--yjit. The manual page also lists-K,-U,-vand--debug, which were not run here, and does not list--backtrace-limit,--parseror the JIT switches, which work. -
Rejected with
invalid switch in RUBYOPT: the ones that select or reshape the program:-e,-c,-C,-x,-S,-s,-a,-l,-n,-p,-i,-0,-F,-h,--versionand--dump. -
The dash of the first option may be omitted (
RUBYOPT=wworks), and several options are separated by spaces. -
Command-line options are processed after
RUBYOPT, so the command line wins:RUBYOPT=-w ruby -W0 w.rbprints no warning whileRUBYOPT=-W0 ruby -w w.rbdoes. -
--disable=rubyopton the command line ignores the variable altogether.
bundle exec is the best-known user of the variable: it sets RUBYOPT to -r/path/to/bundler/setup (plus
BUNDLE_GEMFILE) in the child process so that the Gemfile’s gem set is the only one that require sees:
$ bundle exec ruby -e 'puts ENV["RUBYOPT"], ENV["BUNDLE_GEMFILE"]'
-r/.../lib/ruby/4.0.0/bundler/setup
/.../shelf/Gemfile
Source: Bundler: bundle exec
(The paths are abbreviated.) That is also the explanation for a common surprise: a Ruby program started from inside a
bundle exec shell (a subprocess, a test runner, system("ruby ...")) inherits Bundler’s environment and cannot load gems that
are not in the bundle. Bundler.with_unbundled_env removes it. RubyGems and Bundler are in
RubyGems and Bundler.
RUBYLIB, RUBYPATH and friends
| Variable | Meaning |
|---|---|
|
Directories added to |
|
Directories searched for the program when |
|
The shell command used by |
|
Used by |
|
The pager used for |
|
If set, free all allocated memory at exit (useful with leak checkers such as Valgrind). Ruby 3.3, per the manual page. |
|
Path template of the crash report file written if the interpreter itself crashes; the same as |
Runtime variables
| Variable | Meaning |
|---|---|
|
|
|
|
|
|
|
Maximum native threads of the M:N scheduler (default 8). Ruby 3.3, per the manual page. |
|
Thread time slice in milliseconds (default 100). Ruby 3.4, per the manual page. |
|
VM and machine stack size, in bytes, of new threads (default 1048576 each on 64-bit). |
|
The same for fibers (defaults 131072 and 524288 on 64-bit). |
|
|
|
|
|
The default size of an |
Three of them can be observed directly:
$ ruby -rsocket -e 'p Socket.tcp_fast_fallback'
true
$ RUBY_TCP_NO_FAST_FALLBACK=1 ruby -rsocket -e 'p Socket.tcp_fast_fallback'
false
$ ruby -e 'p IO::Buffer::DEFAULT_SIZE'
65536
$ RUBY_IO_BUFFER_DEFAULT_SIZE=8192 ruby -e 'p IO::Buffer::DEFAULT_SIZE'
8192
$ ruby -e 'p Ruby::Box.enabled?'
false
$ RUBY_BOX=1 ruby -W:no-experimental -e 'p Ruby::Box.enabled?'
true
Ruby::Box is experimental in 4.0 and may change; it is mentioned here only as a switch and described in
Code Loading: require and autoload. RUBY_MN_THREADS is
the entry point to the scheduler of Threads and the GVL; Ractors and
fibers are in Ractors and
Fibers and the Fiber Scheduler.
Stack sizes
The stack-size variables decide how deep a thread’s recursion can go before SystemStackError. A smaller stack lets a program
keep more threads or fibers alive, a larger stack allows deeper recursion; too small a machine stack combined with C extensions
risks a segmentation fault instead of an exception, so change them only with measurements. The numbers below are for the
64-bit macOS build used here and depend on the platform and the size of each frame:
# frozen_string_literal: true
# How deep can a thread recurse before SystemStackError?
depth = 0
recurse = -> { depth += 1 and recurse.call }
Thread.new do
recurse.call
rescue SystemStackError => e
puts "#{e.class}: #{e.message} after #{depth} calls"
end.join
Source: Ruby 4.0 documentation: Thread
$ ruby stack_depth.rb
SystemStackError: stack level too deep after 13104 calls
$ RUBY_THREAD_VM_STACK_SIZE=65536 ruby stack_depth.rb
SystemStackError: stack level too deep after 816 calls
$ RUBY_THREAD_VM_STACK_SIZE=4194304 ruby stack_depth.rb
SystemStackError: stack level too deep after 52426 calls
Source: Ruby 4.0 documentation: Thread
Garbage-collector variables
The garbage collector reads a family of RUBY_GC_* variables at start-up. The page
Garbage Collection and Memory explains the heap, generations
and the tools (GC.stat, GC.config); the table is the reference of the variables in the 4.0.7 manual page:
| Variable | Meaning | Default |
|---|---|---|
|
Initial slots of one slot-size heap (see the keys of |
none |
|
Slots kept free after a collection. |
4096 |
|
Multiplier for heap growth (minimum 1.0, no growth). |
1.8 |
|
Upper bound of slots added at once (0 means no limit). |
0 |
|
A full collection runs when old objects exceed this factor times the count after the last full collection. |
2.0 |
|
Ratio of old objects used to bound remembered write-barrier-unprotected objects. Ruby 3.3 and later. |
0.1 |
|
Free-slot ratios that trigger adding pages, freeing pages, and the target after growth. |
0.2, 0.4, 0.65 |
|
Young-generation |
16 MB, 32 MB, 1.4 |
|
The same for the old generation (a full collection). |
16 MB, 128 MB, 1.2 |
Source: Ruby 4.0 documentation: GC
The malloc limits are observable:
# frozen_string_literal: true
stat = GC.stat
puts "malloc limit #{stat[:malloc_increase_bytes_limit]} bytes"
puts "old malloc limit #{stat[:oldmalloc_increase_bytes_limit]} bytes"
Source: Ruby 4.0 documentation: GC
$ ruby gc_limit.rb
malloc limit 16777216 bytes
old malloc limit 16777216 bytes
$ RUBY_GC_MALLOC_LIMIT=1000000 RUBY_GC_OLDMALLOC_LIMIT=2000000 ruby gc_limit.rb
malloc limit 2271519 bytes
old malloc limit 2400000 bytes
Source: Ruby 4.0 documentation: GC
The reported limits are not exactly the requested values (the first is adjusted at start-up; the second is the requested
2000000 multiplied by its growth factor 1.2), but they move with the variables. A Ruby 3.2 to 3.3 migration note, verified by running both: on 3.2
RUBY_GC_HEAP_INIT_SLOTS enlarges the initial heap, on 3.3 and later it is ignored and the per-heap
RUBY_GC_HEAP_0_INIT_SLOTS (to _4_) takes its place. Tuning the collector is a measured activity: change one variable at a
time and compare GC.stat and the run time, see
Performance, Benchmarking and Profiling.
RubyGems, Bundler and the locale
RubyGems reads its own variables (they are documented by gem help environment and the RubyGems guides); the ones a developer
meets constantly:
| Variable | Meaning |
|---|---|
|
The directory where gems are installed ( |
|
Directories searched for gems, in addition to |
|
The Gemfile to use instead of searching upwards from the current directory. Also set by |
|
Every Bundler setting has a |
|
The locale; it determines the default external encoding (see above). |
Source: Ruby 4.0 documentation: Gem
$ ruby -e 'p Gem.dir'
"/.../ruby/4.0.7/lib/ruby/gems/4.0.0"
$ GEM_HOME=$PWD/gh ruby -e 'p Gem.dir'
"/.../w/gh"
$ GEM_PATH=$PWD/gp ruby -e 'p Gem.path.first(2)'
["/.../w/gp", "/.../ruby/4.0.7/lib/ruby/gems/4.0.0"]
Source: Ruby 4.0 documentation: Gem
(Paths abbreviated.) Setting GEM_HOME to a project directory is the manual way to isolate gems per project; Bundler’s
bundle config set path does the same with a setting instead of an environment variable. The detail is in
RubyGems and Bundler and in
Creating and Publishing Gems, and Rake. A secret
(an API key, a token) belongs in an environment variable read with ENV.fetch at run time, never in a file in the repository;
see Security.
Where the books are outdated
Older Ruby books and tutorials describe the interpreter in terms that no longer hold in 4.0:
| Older statement | Ruby 4.0 |
|---|---|
The interpreter parses and runs a script "line by line". |
The whole file is parsed (by Prism since 3.4), compiled to YARV bytecode and then run; |
|
Removed. |
|
|
"Strings are frozen by default since 3.4." |
Not true. 3.4 introduced chilled strings, which warn only with |
|
RubyGems is always loaded (switch it off with |
The default encoding is the locale’s and |
UTF-8 source files are the default; the default external encoding is still the locale’s, and |
Backtraces quote the method name with a backtick and a straight quote, and messages say |
3.4 and 4.0 print |
The standard library is "everything installed with Ruby". |
Some libraries are bundled gems and need a Gemfile entry under Bundler ( |
A checklist for the command line
-
Put the program’s own options after its name, and interpreter options before it (
--if the name could be mistaken for an option). -
Use
ruby -wc file.rbas a cheap syntax and warning check; use-W:deprecatedbefore an upgrade to see what the next version will reject. -
Prefer
-Ilib -rnameover editing$LOAD_PATHin the program, andbundle execover a hand-builtRUBYLIB. -
Treat
RUBYOPTas global: set it on one command (RUBYOPT=-w rake test), not in a shell profile, and unset it when a tool misbehaves (--disable=rubyoptruns without it). -
Give
-ian extension while you learn it, and remember thatString#linesfollows$/, so-0777changes it. -
Enable YJIT with
--yjit,RUBY_YJIT_ENABLE=1orRubyVM::YJIT.enableafter boot; leave ZJIT,Ruby::Boxand theRUBY_GC_*tuning to measured experiments. -
Set
LANG=C.UTF-8(or-E UTF-8) in containers so the default external encoding is not US-ASCII.
References
-
Ruby 4.0 documentation: Ruby Command-Line Options and Option --dump
-
Comments and magic comments, Global variables and RubyVM::InstructionSequence
-
NEWS for Ruby 4.0.0, 3.4.0 (Prism, chilled strings,
Warning.categories) and 3.3.0 (RubyVM::YJIT.enable, M:N threads,--parser)