Ruby 3.5 Feature: Namespace on read
113 comments
·May 12, 2025ezekg
Umofomia
I will share a concrete example where I've recently run into this problem.
In order to make use of OpenStruct, `require 'ostruct'` first needs to be declared. Our code neglected to make that declaration, and we saw failures when it was deployed. This code, however, passed all of our tests. We discovered it was because our testing framework included rspec-expectations, which has a dependency on diff-lcs[1], and diff-lcs itself declares `require 'ostruct'`[2]. Because of this, ostruct was loaded globally before our code was tested, which silently masked the underlying issue.
This being said, I do understand the sentiment that this feature seems superfluous and may introduce unnecessary complication, especially from a Rubyist's point of view. The underlying mental model of Ruby dependency management is different from many other languages, and it's something to keep in mind when coming from other languages that do have scope for declared dependencies.
[1] https://github.com/rspec/rspec-expectations/blob/v3.13.3/rsp... [2] https://github.com/halostatue/diff-lcs/blob/v1.5.1/lib/diff/...
mjbamford
Very helpful example.
halostatue
`diff-lcs` no longer uses `ostruct` as of 1.6.0 (granted, that was released in February).
quesera
Does that mean you develop code without running it locally?
The first execution of new code is in CI?
viraptor
They didn't say where it was deployed. I'd often only test some changes in staging rather than locally, because they rely on some specific other service I don't want to spend time setting up. It's very common.
ljm
Locally you’d have all the dependencies installed. In production you’d leave out development and test dependencies.
null
graypegg
I really like Ruby, I do sort of wish it could focus on it's own identity a bit.
Some new features feel almost prescribed from other languages? Like, RBS and Namespaces specifically... they don't really fit the model of Ruby exactly, but they need to be there so Ruby can be presented as "having" type-safety and a-solution-for-monkey-patching. I'm all for taking inspiration from other places, but Ruby wasn't quite built around the same axioms that other programming languages started from.
princevegeta89
I used Ruby a lot a long time ago and built medium to large scale enterprise apps on the Rails framework before I moved on to other projects involving Golang and Elixir. Somehow I had more fun and more safety working with Elixir, which was only just getting started as a brand new language trying to establish itself.
It was only after working with Go that I realized how much compile-time safety and performance I was missing out on with Ruby.There's no doubt I would use Ruby today again, but I can't imagine going further beyond simple scripts and workflows. I have recently tried building a very small website with Rails, and I was somewhat surprised the framework did not mature all that much, and type safety and IDE support still seem to be iffy. There is so much unwanted magic that I still see with Ruby on Rails in general. I was looking up to Crystal to address some of the issues I described above, but unfortunately it just remained as a less mature language. Elixir was great, but with the experience I have had, I will actually drop heading into the functional programming world. That leaves me with only one choice, which is Golang. I am not a fan of everything it has, but it is fairly easy for my fellow engineers to pick up, and the IDE support has been nothing short of fantastic.
This is really really a big stretch, but I have always been hopeful towards looking for a TypeScript sort of compile-time layer for Ruby. It may probably never happen though, sadly.
jweir
Sorbet helps. We’ve even created a typed HTML template engine and it helps catch a lot but also allows using the Sorbet LSP to rename things and of course correct autocomplete.
brigandish
> I was looking up to Crystal to address some of the issues I described above, but unfortunately it just remained as a less mature language.
I'd be interested to hear what you find lacking in Crystal, too. For me it would be editor support, last time I tried the LSP I couldn't get it to work.
debugnik
> but with the experience I have had, I will actually drop heading into the functional programming world
Could you elaborate on this? It's a valid resolution, but I consider Elixir to be on the pragmatic side of the functional paradigm, so I'm wondering what pushed you away.
viraptor
Almost every recent (in years) Ruby feature is not a novel idea. Even the origin was a Perl/Smalltalk/Lisp crossover so the axioms were pretty well known.
rco8786
Yea generally agree with this take. Feels like we’re getting features shoehorned in.
andruby
I would like to second (or _third_) this opinion.
I've been working with Ruby for 20 years, and I've not needed something like this. This feels like adding a lot of complexity for little practical benefit. The trade-off feels off. I don't think this is worth the additional complexity.
usrbinenv
I disagree. I've also been working with Ruby for almost 20 years and quite a few times I came across a situation where I definitely wanted to wrap my library in a module for name-spacing and it was almost always cumbersome. I'd rather not wrap it and let others (or myself) use the library name-spaced under whatever name they choose. Right now, if you're working on a gem, you have to think really hard how to name it so its top-level classes an modules don't conflict with any other modules and classes found in the Ruby ecosystem.
ezekg
> Right now, if you're working on a gem, you have to think really hard how to name it so its top-level classes an modules don't conflict with any other modules and classes found in the Ruby ecosystem.
Follow gem naming conventions and this is a non-issue -- both FooBar::Record and BazQux::Record can coexist for foo_bar and baz_qux gems, respectively. If a gem is defining other top-level constants outside of their gem module, then that's considered against convention, i.e. bad practice, and the language should not be modified to allow such a thing.
I'd like to hear of a real use case for namespaces that existing conventions don't already solve.
fellowniusmonk
I think byroot got it exactly right with:
>First, I'm not convinced by the motivations:
>
>Avoiding name conflicts between libraries: Applications can require two different libraries safely which use the same module name.
>
>Is this a problem that happens on a regular basis? I believe Ruby has a pretty well established convention for libraries to expose a single module with a name that correspond to their gem name.
I really don't think we want to make it easier for newbies to alter gem naming conventions and run multiple versions of a gem within the same project, this sounds like a genuine nightmare to me. I've found from jumping in to fix broken and crippled rails projects for startups that the fuckup surface area is already high enough.
werdnapk
I've been working with Ruby full time for pretty much 20+ years as well and there's many parts of it I've never used, although when I need one of it's many features, I love that it's there. I just used a refinement for example last month for the first time to help clean up some code that I didn't want polluting top level classes everywhere.
drogus
While I agree with the sentiment here, ie. that Ruby doesn't necessarily need namespaces, I think it's also not necessarily good to base Ruby usage on what Shopify is doing. They have so many expert Ruby devs, and whole teams that write extra tooling, that I'd argue they shouldn't be compared to pretty much most of the usage of Ruby/Rails out there
byroot
Shopify would benefit a ton from "some" namespaces. In a way, packwerk[0] was an attempt at bringing some namespaces benefits.
But I don't personally think Shopify would benefit from this specific implementation of namespaces (a couple colleagues do). I'm personally not even sure Namespace is a proper term to describe that feature. It's more some sort of lightweight sandboxing to me.
Also:
> They have so many expert Ruby devs
If anything, the average Ruby expertise at Shopify is likely noticeably lower than in most Ruby/Rails shop.
poorman
Try using the Pay gem and others that assume a global singleton for a multi-tenant app (as in multiple different websites under different domains with different Stripe API keys). Lots of gems assume this and their configuration is global.
I personally would love to have this feature!
ezekg
Can you expound a bit more? It seems like Pay encapsulates itself pretty well. I'm not sure I really grok how namespaces could improve or fix whatever is broken here.
chao-
I shared my quick take in another comment tree, but I reread and understand the semantics a little better now, and I like it less, especially the notion that an instance of an object might transform when crossing Namespace boundaries. I get that it is similar to refinements in some ways, but this feels like it would be even more surprising, and I can foresee some gnarly bugs coming from this unless it is trivially easy to visualize the changes to an object between namespaces.
I'm updating my opinion from "mixed feelings" to "against" on this.
tobyhinloopen
That's because you are aware of the limitations. Ruby is really powerful and allows you to manipulate existing modules, but you shouldn't do that because it will leak everywhere.
This namespaces feature allows you to manipulate existing globals, but keep it isolated in your own namespace. That seems pretty good to me :) Because it remains isolated, you can also use these features more aggressively.
hosh
I thought being able to do this with Nodejs was a near capability.
Over the past two years, I have come to understand that this contributes to the nightmare that is the Nodejs ecosystem (and the browser JS exosystem in general), at least when it comes to writing reliable software.
irjustin
I wish ruby went with hard/explicit imports like python or js.
Solves this problem and "magic" that so many complain about while retaining all the other great things to love about Ruby.
PufPufPuf
Most definitely. When I worked with existing Ruby codebases, it was surprisingly hard to answer the question "where is this defined" when every import is dumped into a single global namespace. I had no idea where stuff came from, and neither the IDE.
sethrin
Use the `method` method to get a reference to the one you are concerned with, then call `source_location` on it.
irjustin
That's only during runtime. Simply reading code can be annoyingly difficult without hard references. Especially when the code is coming from a gem.
Many times LSP's can't figure out where the code is coming from if it's a few layers deep. Then you're stuck with the time consuming method of running the code and doing something like what you're describing above just to read it.
zoky
I mean, nothing is stopping you from doing something like:
require 'foo'
Bar = Foo::Bar
bm5k
Ew. No.
jacobevelyn
I see a lot of comments here asking about practical motivations for this feature. One I'll share is that in a gem I help maintain, we benchmark git branches of the gem against `main`, and we also benchmark against multiple other gems that sometimes have namespace collisions with each other. To make this work, we use a third-party gem[0] and an anonymous module trick[1] that are each a bit hacky.
That being said, I have no particular stance on whether this feature is a good change to the language; in a decade of Ruby this is the only situation I can recall that really merited it, and the concerns articulated by byroot and others do resonate with me.
[0] https://github.com/panorama-ed/memo_wise/blob/main/benchmark... [1] https://github.com/panorama-ed/memo_wise/blob/main/memo_wise...
baggy_trough
I've been happy with some of the great improvements to Ruby in the past year. But this one really leaves me scratching my head. It seems like a lot of complexity for what I perceive as a negative value feature. I'm concerned about damage to the gem ecosystem by encouraging gems to bundle version locked dependencies. The last thing I want is a gem deciding to have multiple versions of other gems in my app.
chao-
I agree and I am overall mixed on this.
One of the tradeoffs that (imo) has been net positive in the Ruby ecosystem, is how a project has to ultimately load and run a single set of versions of all dependencies. It creates some extra maintenance work on one hand, but the result is that it encourages the ecosystem to not have to face the hell of having 3 or 5 or 10 versions of each common dependency within a project.
I recognize that this is an occasional cost to library maintainers, but in the long-term has contributed to benefits. My perception is that Ruby libraries have smaller list of dependencies than similar libraries in some other languages. There are several reasons for this, but this ecosystem pressure to stay compatible with a range of dependency versions is one of them.
It feels to me like this leads to a sweet spot for small- and medium-sized projects, and I can see it might have an upper limit? I have not been in the situation, but heard of situations where the largest projects inevitably run into needing specific versions of two separate libraries that don't agree on a version of a common dependency.
semiquaver
This is a really interesting feature addressing something Ruby has long-lacked.
But I think the more interesting story is the widespread opposition to the way this was forced through in spite of major conceptual problems, bugs and performance regressions.
monooso
Identifying it as an experimental feature (disabled by default) seems like a reasonable way to make progress and iron out any problems.
frou_dh
I remember Matz gave a presentation touching on how Ruby was inspired by Lisp and particularly Emacs Lisp, which was the one he had access to. You still can feel a similarity up to now, with the single big old dynamic namespace where you can monkey-patch anything if you feel like it.
sctb
Even this proposed feature is very similar to Common Lisp packages.
nomilk
If video exists of the presentation, would love to see it.
felipemesquita
This post (2019) by David Bryant Copeland about npm security goes into some of the complications that arise when multiple versions of a dependency are allowed to be loaded simultaneously:
https://naildrivin5.com/blog/2019/07/10/the-frightening-stat...
usrbinenv
To add to my other comments ITT, I was thinking how the proposed name-spacing in Ruby would work is, in one way, diametrically different from Go where you'd normally import a package with the name that was chosen by the package author. But, at the same time, you could choose to import it under a different name or with no namespace at all, using the dot prefix. I'm not sure which is better. I suppose I could agree Go has a better default when it comes to namespaces.
vidarh
It's not straightforward to prevent the original name from existing, though you can play games with File.read() and class_eval, but if you just want it to be available under a different name, and don't mind that the old one is there too, you can just do:
require 'foo'
module MyPreferredNamespace
include Foo
end
Alifatisk
First the Data class and now this, very interesting to see! Is there any certain use case where this is well suitable? Because we already have Modules and from my understanding, can be used to namespace things. Or is the appeal here that you can perform the namespacing dynamically through ”require”?
choward
Ruby doesn't have real modules. You're right, that they are just namespaces. They are global variables with extra typing.
If you use a third party library, when you require something, you have no idea what "modules" or other values it creates polluting the global namespace. This at least assigns those values to a local variable where they can be accessed and doesn't make them global.
That said, I'm not a fan of ruby and all the workarounds to try to make it like a more sane language that it isn't.
vinceguidry
It allows you to namespace things that aren't themselves namespaced. That way you can just require things that, might say, have the same module names as your own code without worry. I'm curious if this namespace mechanism could, say, isolate monkey patches. Probably not, but it would be nifty.
tagomoris
Namespace isolates monkey patches.
chao-
>I'm curious if this namespace mechanism could, say, isolate monkey patches.
Wasn't that the purpose of refinements? Perhaps I am misremembering, because I never had a need to reach for refinements myself.
vinceguidry
You still need to rely on the library maintainers to use them. This mechanism allows you to sandbox a library that doesn't.
usrbinenv
Using Ruby modules to namespace things is certainly possible, but is very often cumbersome. Modules also "hardcode" a namespace: that is, if I use modules for name-spacing, it will always be "MyModule::MyClass" everywhere -- or, in other words, namespacing is controlled by library author, not user. The proposed feature shifts that control to library user.
vidarh
> if I use modules for name-spacing, it will always be "MyModule::MyClass" everywhere
It will be identified as such by e.g. the default `inspect`, but nothing stops you from `include`-ing MyModule into another module or class, or even the global scope so that you can reference MyClass without MyModule.
benatkin
I think in a way it's similar to ShadowRealm in JavaScript. https://github.com/tc39/proposal-shadowrealm/blob/main/expla...
dudeinjapan
I'm against this because it will be abused, and cause chaos vis-a-vis well-established conventions in the Ruby world.
The example given in the ticket, defining a global constant PORT in the root namespace and then loading it separately into two apps--nobody actually does in Ruby this today, because it is well-known that it will cause conflicts. Any gem (Ruby library) that did this sort of thing has long-since been barked at and corrected--the solution is simply to nest the constant inside your main module e.g. MyGem::PORT.
Now, gems will be free to define whatever constants/modules/etc. they want. When someone files an issue, the response can be: "Oh you don't like that I redefined Array? Load my gem in a namespace!"
I feel like this is a solution to a problem nobody really has in practice, by simply following conventions, and I've been using Ruby for over 10 years. If byroot -- who works at Shopify on the largest Rails codebase in existence -- echos the same sentiment, then maybe it should be scrapped.