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
-
It raises
TypeError -
[Net::HTTP, Class] -
[HTTP, Module]Correct -
[HTTP, Class]
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
require 'net/http'defines the classNet::HTTPinside theNetmodule.include NetmixesNetintoObject, so constant lookup for the bare nameHTTPnow findsNet::HTTPthrough the ancestor chain. At this pointHTTPandNet::HTTPare the same object, and it is aClass.module HTTP ... enddefines the constantHTTPdirectly onObject. Since Ruby 3.2, amodule(orclass) keyword only reopens a constant defined in the lexical scope itself, not one merely inherited through an included module. Ruby therefore creates a new, emptyModuleinstance.- The bare name
HTTPnow resolves to the new module first, because a constant defined onObjectitself takes precedence over one found inObject's ancestors.Net::HTTPis 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.