LaunchKit
All quizzes
Ruby 0 views

Shadowing Net::HTTP with your own module

Question

What's the return value of the following Ruby 3.2 code?

require 'net/http'

[Net::HTTP, Net::HTTP.class] # => [Net::HTTP, Class]

include Net

[HTTP, HTTP.class] # => [Net::HTTP, Class]

module HTTP
end

[HTTP, HTTP.class] # => ???

The correct answer is

Explanation

TL;DR

Since Ruby 3.2, module HTTP defines a brand-new module in the current namespace even when the constant HTTP is already resolvable through an included module. The new top-level HTTP module shadows the inherited Net::HTTP, so the last line returns [HTTP, Module]. On Ruby 3.1 and earlier, the same code raised TypeError because Ruby tried to reopen the class Net::HTTP as a module.

Step by step

This snippet runs on Ruby 3.2 or later:

require 'net/http'

[Net::HTTP, Net::HTTP.class] # => [Net::HTTP, Class]

include Net

[HTTP, HTTP.class] # => [Net::HTTP, Class]

module HTTP
end

[HTTP, HTTP.class]      # => [HTTP, Module]
HTTP.equal?(Net::HTTP)  # => false
  1. require 'net/http' defines the class Net::HTTP inside the Net module.
  2. include Net mixes Net into Object, so constant lookup for the bare name HTTP now finds Net::HTTP through the ancestor chain. At this point HTTP and Net::HTTP are the same object, and it is a Class.
  3. module HTTP ... end defines the constant HTTP directly on Object. Since Ruby 3.2, a module (or class) keyword only reopens a constant defined in the lexical scope itself, not one merely inherited through an included module. Ruby therefore creates a new, empty Module instance.
  4. The bare name HTTP now resolves to the new module first, because a constant defined on Object itself takes precedence over one found in Object's ancestors. Net::HTTP is untouched and still reachable under its qualified name.

Version notes

On Ruby 3.1 and earlier, step 3 behaved differently: constant lookup found the inherited Net::HTTP, and module HTTP attempted to reopen that constant. Reopening a Class with the module keyword is invalid, so the definition raised an error (do not expect this on a modern interpreter):

TypeError (HTTP is not a module)

The symmetric trap existed too: class Foo raised TypeError when an included module already provided a module named Foo. Ruby 3.2 removed both traps: definitions no longer reopen constants that come from included modules, which keeps third-party constants safe from accidental modification.

Share this quiz

Comments

No comments yet. Be the first.

Only used to confirm and publish your comment. Never shown publicly, never shared.

Markdown: **bold**, `code`, ```fenced blocks```, > quotes, [links](url). HTML and images are not rendered.