On every invocation of require "enumerator" (for example during boot when many gems require it), the VM will scan the load path twice: once for enumerator.rb and again for enumerator.so. Of course, no file is found because enumerator is now shipped within the VM by default.
In enumerator.c, we call rb_provide("enumerator.so") which adds it to $LOADED_FEATURES. This means require "enumerator.so" can be optimized, but most libraries do not include the .so extension when requiring enumerator.
If we want to allow loading an enumerator.rb library, then we cannot avoid the scan of the load path for *.rb files. However, we can avoid scanning the load path twice, once for enumerator.rb and once for enumerator.so, since we already know we loaded enumerator.so. I've submitted a pull request to do that: https://github.com/ruby/ruby/pull/4687
Avoid pointless attempts to open .so file if already required
When attempting to require a file without an extension that has
already been required or provided with an .so extension, only
look for files with an .rb extension. There is no point in
trying to find files with an .so extension, since we already
know one has been loaded.
Previously, attempting to require such a file scanned the load
path twice, once for .rb and once for .so. Now it only scans
once for .rb. The scan once for .rb cannot be avoided, since
the .rb file would take precedence and should be loaded if it
exists.
IMHO it would be fine to simply not allow loading any foo.rb if foo.so is already loaded, a change of semantics which would gain a lot of simplicity.
Do we have actual use cases for require "foo" to first load foo.so and then later on another require "foo" to load foo.rb?
It seems unlikely to be intentional behavior.
I argue users should at least specify the extension in both cases if they expect that.