In Ruby, how do RUBY_ENGINE, RUBY_ENGINE_VERSION and RUBY_PLATFORM tell code which engine and platform it runs on?
answer
- engine name vs language version
- "ruby", "jruby", "truffleruby"
- engine version equals RUBY_VERSION on CRuby
- RUBY_PLATFORM is "java" on JRuby
- RbConfig::CONFIG["host_os"] for the OS
basics
~10 sRUBY_ENGINE names the engine ("ruby" for CRuby, "jruby", "truffleruby"), RUBY_ENGINE_VERSION is that engine's own release, and RUBY_PLATFORM is the build's CPU-OS string, which is just "java" on JRuby.
solid answer
~30 s`RUBY_ENGINE` is the engine's name: `"ruby"` on CRuby, `"jruby"`, `"truffleruby"`. `RUBY_VERSION` is the Ruby **language** version the engine implements, and `RUBY_ENGINE_VERSION` is the engine's **own** release number; on CRuby the two are the same string, on JRuby and TruffleRuby they differ. `RUBY_PLATFORM` is the platform the interpreter was built for, such as `"x86_64-linux"` on CRuby, but it is simply `"java"` on JRuby, so for the operating system read `RbConfig::CONFIG["host_os"]`, as RubyGems' own `Gem.win_platform?` does. Branch on the engine only where behaviour really differs; Ruby 4.0 also exposes the same values as `Ruby::ENGINE`, `Ruby::VERSION` and `Ruby::PLATFORM`.
code
ruby · 11 linesrequire "rbconfig"
engine = case RUBY_ENGINE
when "ruby" then "CRuby #{RUBY_VERSION}"
when "jruby" then "JRuby #{RUBY_ENGINE_VERSION} (Ruby #{RUBY_VERSION})"
when "truffleruby" then "TruffleRuby #{RUBY_ENGINE_VERSION} (Ruby #{RUBY_VERSION})"
else RUBY_ENGINE
end
windows = RbConfig::CONFIG["host_os"].match?(/mswin|mingw/)
puts "#{engine}, platform #{RUBY_PLATFORM}, windows=#{windows}"go deeper
Recall the three values: RUBY_ENGINE is "ruby" on CRuby, RUBY_VERSION is the language version, and RUBY_PLATFORM is the build's platform string.
Explain why RUBY_ENGINE_VERSION equals RUBY_VERSION only on CRuby, why RUBY_PLATFORM is "java" on JRuby, and which source gives the OS instead.
Show where engine branches are justified, such as native versus FFI backends or platform-specific gems, and push everything else toward capability checks that survive a move to another engine.
Treat multi-engine support as a cost: every RUBY_ENGINE branch is a code path needing CI on that engine, so decide explicitly which engines a library or service supports.
## Three constants, three different questions Ruby defines a small set of top-level constants at boot that describe the running interpreter. They answer different questions and are easy to mix up: - **`RUBY_ENGINE`** - which implementation is running: `"ruby"` for CRuby (MRI), `"jruby"` for JRuby, `"truffleruby"` for TruffleRuby. - **`RUBY_VERSION`** - which version of the Ruby **language** the engine implements, for example `"4.0.7"`. - **`RUBY_ENGINE_VERSION`** - the engine's **own** release number. CRuby defines it from the same string as `RUBY_VERSION`, so they are always equal there. On JRuby or TruffleRuby it is the engine's release, which follows its own numbering and does not match the language version. - **`RUBY_PLATFORM`** - the platform string the interpreter was built for, such as `"x86_64-linux"` or an `arm64-darwin` string on CRuby. - **`RUBY_DESCRIPTION`** - the full line `ruby -v` prints, including markers such as `+PRISM` for the active parser. ## The JRuby platform trap On JRuby, `RUBY_PLATFORM` is the literal string `"java"`: the "platform" is the JVM, not the operating system. RubyGems relies on this: `Gem.java_platform?` is simply `RUBY_PLATFORM == "java"`. The consequence is that OS detection written as `RUBY_PLATFORM.include?("linux")` or `=~ /mingw/` silently gives the wrong answer on JRuby. For the operating system, read the build configuration instead: 1. `require "rbconfig"` (RubyGems already loads it in a normal run). 2. Match `RbConfig::CONFIG["host_os"]` against patterns such as `/mswin|mingw/` or `/darwin/`. 3. Or call `Gem.win_platform?`, which does exactly that match on `host_os` rather than on `RUBY_PLATFORM`. ## When to branch on the engine at all Engine checks belong only where engines genuinely differ. The default gems do it sparingly, which is a good model: - **Prism** picks its backend with `if RUBY_ENGINE == "ruby"`: the C extension on CRuby, an FFI binding elsewhere (`Prism::BACKEND` records which). - **RubyGems and Bundler** check `RUBY_ENGINE == "jruby"` or `"truffleruby"` for platform matching, because JRuby installs `java`-platform gems while TruffleRuby builds `ruby`-platform gems from source. - **`open3`** loads a JRuby-specific helper only when `RUBY_ENGINE == "jruby"` and the JVM runs on Windows. Everywhere else, prefer testing for the capability you need. A check like `defined?(RubyVM)` works as a CRuby test (the `RubyVM` namespace exists only on MRI), but it hides the intent, while `RUBY_ENGINE == "ruby"` says what it means. Two anti-patterns come up in reviews: - Comparing `RUBY_ENGINE_VERSION` to decide whether a **language** feature exists. Features follow the language version, so on JRuby you would be comparing the wrong number. - Treating `RUBY_ENGINE == "ruby"` as "not Windows". The engine says nothing about the OS; CRuby builds exist for Windows too. ## Reading the values in a console A quick way to answer "what exactly is this process running?" is to print all of them at once; the output below is from CRuby 4.0.7 on Linux: ```ruby RUBY_ENGINE # => "ruby" RUBY_VERSION # => "4.0.7" RUBY_ENGINE_VERSION # => "4.0.7" RUBY_PLATFORM # => "x86_64-linux" (varies by build) ``` On JRuby the first line reads `"jruby"`, `RUBY_ENGINE_VERSION` shows JRuby's own release, and `RUBY_PLATFORM` reads `"java"`. Logging these four values at boot, together with `RUBY_DESCRIPTION`, is cheap and settles many "works on my machine" arguments, because it also records whether the build parsed with Prism. ## What changed in Ruby 4.0 Ruby 3.4 reserved the top-level name `Ruby`, and **Ruby 4.0 defines a `Ruby` module** that holds these values as `Ruby::ENGINE`, `Ruby::ENGINE_VERSION`, `Ruby::VERSION`, `Ruby::PLATFORM`, `Ruby::DESCRIPTION` and friends. CRuby defines each value once and publishes it both inside the module and as the old top-level `RUBY_` name, so `Ruby::ENGINE` and `RUBY_ENGINE` are the very same frozen string. Nothing about the `RUBY_` constants is deprecated; code that must also run on older Rubies should keep using them. | Constant | CRuby 4.0.7 | JRuby | Use it for | |---|---|---|---| | `RUBY_ENGINE` | `"ruby"` | `"jruby"` | engine-specific code paths | | `RUBY_VERSION` | `"4.0.7"` | the language version it implements | language-feature checks | | `RUBY_ENGINE_VERSION` | `"4.0.7"` | JRuby's own release | engine bug workarounds | | `RUBY_PLATFORM` | e.g. `"x86_64-linux"` | `"java"` | build platform, not the OS | ## What interviewers listen for A good answer separates **engine**, **language version** and **platform**, knows the `"java"` trap, and reaches for `RbConfig::CONFIG["host_os"]` or `Gem.win_platform?` for the OS.
- On Ruby 4.0, is there any difference between Ruby::ENGINE and RUBY_ENGINE?No. CRuby 4.0 defines the value once and publishes it both as `Ruby::ENGINE` and as the top-level `RUBY_ENGINE`, so they are the same frozen string. The `Ruby` module was only reserved in 3.4, so code that must also run on 3.x should keep the `RUBY_` names.
- Why is defined?(RubyVM) a weaker CRuby check than RUBY_ENGINE == "ruby"?It does work, because the `RubyVM` namespace exists only on MRI, but it tests an implementation detail and hides the intent. Any other code could define a `RubyVM` constant, and a reader has to know the trick. `RUBY_ENGINE == "ruby"` states exactly what is being checked.
saying these in an interview costs you the question
- RUBY_ENGINE_VERSION tells you which language features are available
- RUBY_PLATFORM names the operating system on every Ruby engine
- RUBY_ENGINE is "mri" when running on CRuby
- Ruby 4.0 deprecated RUBY_ENGINE in favour of Ruby::ENGINE
- RUBY_ENGINE == "ruby" means the process is not on Windows