In Ruby, what is the difference between $stdout and STDOUT, and which one should you reassign to redirect output?
answer
- one is a variable, one a constant
- Kernel#puts consults the global
- the constant keeps the original stream
- setter demands a write method
- warn goes to $stderr
basics
~10 s$stdout is a global variable holding the current standard output stream, and Kernel#puts writes to it; STDOUT is a constant holding the original stream. Redirect by reassigning $stdout; STDOUT stays as the way back.
solid answer
~40 sRuby names each standard stream twice. `$stdin`, `$stdout` and `$stderr` are **global variables** holding the *current* streams; `STDIN`, `STDOUT` and `STDERR` are **constants** holding the *original* ones, and at start-up each pair points at the same `IO` object. `Kernel#puts` and `print` write to whatever `$stdout` holds at the moment of the call, and `Kernel#warn` writes to `$stderr`, so reassigning the global redirects them. Its setter accepts any object that responds to `write` (a `File`, a `StringIO`) and raises `TypeError` otherwise. Reassigning `STDOUT` only earns an "already initialized constant" warning and redirects nothing, because `puts` never reads the constant; code that writes to `STDOUT` directly bypasses any redirection on purpose.
code
ruby · 15 lines$stdout.equal?(STDOUT) # => true at start-up
log = File.open("run.log", "a")
saved = $stdout
begin
$stdout = log
puts "goes to run.log" # Kernel#puts reads $stdout
STDOUT.puts "still on the terminal"
warn "goes to $stderr"
ensure
$stdout = saved
log.close
end
$stdout = nil # TypeError: $stdout must have write method, NilClass givengo deeper
Recall that $stdout is the current stream and STDOUT the original, that puts writes to $stdout, and that warn writes to $stderr.
Explain that Kernel methods look the global up at call time, that the setter only demands a write method, and why reassigning STDOUT redirects nothing.
Show where a global swap stops working: shared across threads, invisible to child processes and descriptor-level writers, and bypassed by code that writes to STDOUT directly.
Argue for injecting output streams into components instead of relying on a process-wide global, and set a rule for when library code may write to the constants.
## Six names for three streams Every process starts with three **standard streams**: standard input (file descriptor 0), standard output (descriptor 1) and standard error (descriptor 2). Ruby wraps each in an `IO` object and exposes it under two names: | Stream | Global variable (current) | Constant (original) | Descriptor | |---|---|---|---| | standard input | `$stdin` | `STDIN` | 0 | | standard output | `$stdout` (alias `$>`) | `STDOUT` | 1 | | standard error | `$stderr` | `STDERR` | 2 | When the interpreter boots, each global and its constant refer to the **same object**: `$stdout.equal?(STDOUT)` is `true`. The difference is what happens later. A **global variable** can be rebound at any time; a **constant** is meant to be set once, and CRuby's own comment on `STDOUT` in `io.c` calls it the holder of "the original stdout". ## Who reads the globals The convenience methods in `Kernel` look the stream up **on every call**: - `Kernel#puts` is documented as equivalent to `$stdout.puts(objects)`, and `print` goes to the same place. - `Kernel#warn` hands its message to `Warning.warn`, whose default implementation writes to the current `$stderr`. When warnings are disabled (`ruby -W0` sets `$VERBOSE` to `nil`), `warn` prints nothing at all. - `Kernel#gets` reads through `ARGF`, which reads the current `$stdin` when `ARGV` holds no file names at the first read. Because the lookup happens at call time, rebinding the global is enough to redirect every later `puts`, `print` and `warn` in the process, including calls made deep inside library code. ## Rules for reassigning `$stdout` 1. **Anything with a `write` method is accepted.** The setter checks with `respond_to?`-style logic, so a `File`, a `StringIO` or your own object that implements `write` all work. `$stdout = nil` raises `TypeError` with the message "$stdout must have write method, NilClass given". The same check guards `$stderr`. 2. **It rebinds a name; it does not touch the old stream.** The previous `IO` is neither closed nor flushed, and any object that stored a reference to it keeps writing there. 3. **It is shared by every thread.** In CRuby the variable is per-Ractor, which in ordinary programs means one value for the whole process, so two threads that redirect it at the same time trample each other. 4. **It is a Ruby-level change, not a descriptor change.** File descriptor 1 still points where it did. A child process, or C code that writes straight to descriptor 1, is unaffected. Redirecting the descriptor itself is what `IO#reopen` does. The usual shape keeps the previous value in a local and restores it in `ensure`, so nested redirections unwind correctly: save `$stdout`, assign the new stream, run the code, and put the saved value back even if an exception escapes. ## Why not reassign `STDOUT` Assigning to `STDOUT` is a constant reassignment. Ruby allows it with the warning "already initialized constant STDOUT", and it changes nothing that matters: `puts` never consults the constant, so output still goes to `$stdout`. What you *have* done is lose the one name that always meant "the real terminal or pipe". Keeping the constant untouched gives it two legitimate uses: - **Restoring**: `$stdout = STDOUT` puts the process back to its original stream when there is no saved value to restore. - **Deliberate bypass**: a progress bar, an interactive prompt or a crash reporter that must reach the real output even while a caller has redirected `$stdout` can write to `STDOUT` or `STDERR` directly. That second use cuts both ways. Library code that writes to `STDOUT` instead of `$stdout` cannot be silenced or captured by its caller, which is why the convention is to write to the globals (or to an injected stream) and reserve the constants for the rare case that must escape redirection. ## Quick reference | Goal | Write this | |---|---| | Send all later `puts` output to a log file | `$stdout = File.open("run.log", "a")` | | Capture output in memory | `$stdout = StringIO.new` | | Put the original stream back | `$stdout = STDOUT`, or a saved local | | Print to the terminal even while redirected | `STDOUT.puts` | | Report a diagnostic | `warn "..."`, which lands on `$stderr` |
- Why would library code deliberately write to STDERR rather than $stderr?To reach the process's real error stream even when a caller has swapped `$stderr` for a buffer, for example a crash reporter that must be seen. The cost is that callers can no longer capture or silence that output, so it should be reserved for messages that must escape redirection; ordinary diagnostics belong on `$stderr` or through `warn`.
- Does reassigning $stdout redirect the output of a command started with system?No. The global swap changes which Ruby object `puts` calls; the child process inherits file descriptor 1, which still points at the original terminal or pipe. Redirecting a child needs the descriptor itself redirected, with `IO#reopen` or the spawn options; capturing subprocess output is its own topic.
- What does $> refer to?`$>` is another name for the same current standard output: it shares `$stdout`'s getter and setter, so assigning either changes both. Its English-library name is `$DEFAULT_OUTPUT`. Most code writes `$stdout` because it reads more clearly.
$stdout is the forwarding address on your mail: change it and every later letter goes to the new place. STDOUT is the house's street address, which never changes, so you can always forward the mail back home.
saying these in an interview costs you the question
- Reassigning STDOUT is how you redirect a script's output
- Kernel#puts always writes to STDOUT whatever $stdout holds
- Only an IO or File object can be assigned to $stdout
- Reassigning $stdout also redirects child processes started with system
- warn writes to standard output just like puts