Ruby 4.0.0
249 points
5 hours ago
| 13 comments
| ruby-lang.org
| HN
maz1b
2 hours ago
[-]
It's never Christmas without a new ruby version.

The ruby::box thing looks pretty interesting, from a cursory glance you can run two simultaneous versions of something like a feature or rollout much more conveniently.

Also being able to do

  if condition1
     && condition2
    ...
  end

on multiple lines rather than one - this is pretty nifty too!
reply
WJW
1 minute ago
[-]
I'm kinda hoping that eventually each ractor will run in it's own ruby::box and that each box will get garbage collected individually, so that you could have separate GCs per ractor, BEAM-style. That would allow them to truly run in parallel. One benefit should be to cut down p99 latency, since much fewer requests would be interrupted by garbage collection.

I'm not actually in need of this feature at the moment, but it would be cool and I think it fits very well with the idea of ractors as being completely separated from each other. The downside is of course that sharing objects between ractors would get slower as you'd need to copy the objects instead of just sharing the pointer, but I bet that for most applications that would be negligible. We could even make it so that on ractor creation you have to pass in a box for it to live in, with the default being either a new box or the box of the parent ractor.

reply
tebbers
2 hours ago
[-]
I've been doing

  if condition1 && 
       condition2
       ...
  end
for ages and it seems to work find, what am I missing with this new syntax?!
reply
AlecSchueler
38 minutes ago
[-]
I prefer the new way because if you want to remove one condition you just delete the line rather than having to edit in multiple places.
reply
Sammi
2 hours ago
[-]
Less likely to cause git merge conflict as you don't change the original line. You only add one.
reply
pmontra
13 minutes ago
[-]
What we need is a syntax aware git diff https://news.ycombinator.com/item?id=42093756
reply
fuzzythinker
1 hour ago
[-]
In languages where placement don't matter, like c/js, I prefer leading booleans. It makes it much easier to see the logic, especially with layers of booleans.
reply
mantas
2 hours ago
[-]
Personally && in the new line seems to be much better readability. Can’t wait to use some smart cop to convert all existing multiline ifs in my codebase.
reply
digitaltrees
1 hour ago
[-]
Ruby is amazing. I recently built a layer on top of Rails that can generate an API from a single markdown file. I did the same thing in python but it was much harder and JavaScript would have been a beast. Ruby can meta program like nothing else.
reply
mierz00
1 hour ago
[-]
Curious to hear more about this, do you have any examples?
reply
aaronbrethorst
4 hours ago
[-]
It wouldn't be Christmas without a new version of Ruby. Thanks Matz and co!
reply
matltc
1 hour ago
[-]
Glad to see internal stack traces cleaned up (maybe we can get relative paths some day?) and Set finally get the respect it deserves!
reply
Alifatisk
24 minutes ago
[-]
Relative path in stack trace would be so good!
reply
RomanPushkin
2 hours ago
[-]
I'm happy to see v4.0, but 2025 was the year I switched from Ruby to Python after gradually drifting back to it more and more. The tipping point was when I had Claude Code automatically convert one of my Ruby projects to 100% Python - and after that, I just had no Ruby left.

I spent over a decade enjoying Ruby and even wrote a book about it. At this point, though, Python has won for me: fastapi, pytorch, langchain, streamlit, and so on and on.

It's a bit sad, but I'll always remember the Christmas gifts, and the syntax that is always so much better than Python.

reply
Bayko
17 minutes ago
[-]
Langchain? I tried using/learning langchain then I found out that it was evolving so fast that even the latest ai models didn't have even remotely up to date information on it! Not to mention the hundreds of Google search results for ---- why do langchain docs suck? I finally switched to haystack and I have been really happy. (Don't work on corporate ai software this is just for personal use)
reply
0xblinq
18 minutes ago
[-]
Just by how much better the editor/IDE support is with Python it is a change worth to do.

I just can’t stand the excessive dynamism of Ruby. I understand some people prefer/enjoy it, it’s just not for me.

reply
nurettin
17 minutes ago
[-]
Used Ruby for a decade, knew about it for more than that. I still sometimes use ruby syntax to communicate ideas with friends and colleagues.

For me, the killer feature of Python was the typing module and the intellij pycharm community edition being free and RubyMine having a subscription fee.

reply
lcnmrn
2 hours ago
[-]
You should try Falcon too.
reply
Bolwin
1 hour ago
[-]
Have they improved tooling? I've yet to get any lsp working on windows
reply
Alifatisk
20 minutes ago
[-]
It’s below expectations, and even worse on Windows.

Luckily people seem to be aware of this and there was a whole talk about improving Ruby DX.

reply
ornornor
1 hour ago
[-]
IMO programming on windows is just asking for punishment. Unless it’s a Microsoft language, you’re so much better off on Linux or macOS.
reply
nurettin
15 minutes ago
[-]
To be fair, vscode and .net core on linux is pretty much like having windows and visual studio at this point.
reply
nchmy
1 hour ago
[-]
Or wsl2
reply
mikestorrent
3 hours ago
[-]
Still love Ruby deeply even though I now work somewhere where it's not in use. Thanks for the release, I hope I find a reason to use it!
reply
ksec
3 hours ago
[-]
It seems Ractor is still work in progress while Fiber has matured a lot in the last few releases.

I vaguely remember reading Shopify is using Fiber / Rack / Async in their codebase. I am wondering if Rails will get more Fiber usage by default.

reply
byroot
21 minutes ago
[-]
> It seems Ractor is still work in progress

The Ractor experimental status could almost be removed. They no longer have known bugs, and only one noticeable performance issue left (missing Ractor local GC).

But the API was just recently changed, so I think it's better to wait another years.

> I vaguely remember reading Shopify is using Fiber / Rack / Async in their codebase.

Barely. There is indeed this management obsession for fibers even when it doesn't make sense, so there is some token usage there and there, but that's it.

There is one application that was converted from Unicorn to Falcon, but falcon isn't even configured to accept concurrent requests, the gain is basically 0.

As for Rails, there isn't much use cases for fibers there, except perhaps Active Record async queries, but since most users use Postgres and PG connections are extremely costly, few people are using AR async queries with enough concurrency for fibers to make a very noticeable difference.

reply
shevy-java
2 hours ago
[-]
To me it seems very few people use ractors. A bit more use fibers though.

It's a bit of a mess IMO. I'd much prefer everything be simplified aggressively in regards to threads + GIL; and Ractors integrated on top of Ruby::Box to provide not only namespaced container-like entities but also thread-support as a first-class citizen at all times. The API of ractors is weird and really not fun to use.

reply
Lammy
1 hour ago
[-]
I really enjoyed using them for my Ruby file-matching library where I wanted to read `shared-mime-info` XML source package files directly and on the fly as opposed to using the pre-processed secondary files that the upstream `update-mime-database` tool spits out. The problem is that a type definition can be spread out over multiple XML packages in both system and user paths, so the naïve implementation of reading them all at once wastes a massive amount of memory and a massive number of object allocations (slow) when most people use maybe 5% of the full set of supported types (the JPEGs and HTMLs and ZIPs of the world).

I wanted to read the source package files directly because I always found `shared-mime-info`'s usual two-step process for adding or editing any of the XML type data to be annoyingly difficult and fragile. One must run `update-mime-database` to decompose arbitrarily-many XML packages into a set of secondary files, one all-file-extensions, one all-magic-sequences, one all-aliases, etc. System package managers usually script that step when installing software that come with their own type data. I've accidentally nuked my entire MATE session with `update-mime-database` before when I wanted to pick up a manual addition and regenerated the secondary files while accidentally excluding the system path that had most of the data.

I ended up doing it with four Ractors:

- a Ractor matching inputs (MIME Type strings, file extensions, String or Pathname or URL paths for sniffing) against its loaded fully-formed type definition objects.

- a Ractor for parsing MIME Type strings (e.g. "application/xml") into Hash-keying Structs, a task for which the raw String is unsuitable since it may be overloaded with extra syntax like "+encoding_name" or fragment ";key=value" pairs.

- a fast XML-parser Ractor that takes in the key Structs (multiple at once to minimize necessary number of passes) and figures out whether or not any of those types are defined at all, and if so in which XML packages.

- a slow XML-parser Ractor that takes the same set of multiple key Structs and loads their full definition into a complete type object, then passes the loaded objects back to the matcher Ractor.

The cool part of doing it this way is that it frees up the matcher Ractor to continue servicing other callers off its already-loaded data when it gets a request for a novel type and needs to have its loader Ractors do their comparatively-slow work. The matcher sets the unmatched inputs aside until the loaders get back to it with either a loaded type object or `nil` for each key Struct, and it remembers `nil`s for a while to avoid having to re-run the loading process for inputs that would be a waste of time.

The last pre-Ractorized version allocated around 200k objects in 7MiB memory and retained 17k objects in 2MiB of memory for a benchmark run on a single input, with a complete data load. The Ractorized version was twice as fast in the same synthetic benchmark and allocated 20k objects in 2MiB of memory and retained 2.5k objects in 260KiB of memory for its initial minimal data load. I have it explicitly load `application/xml` and `application/zip` since those combined are the parent types for like a third of all the other types, and a few other very common types of my choosing.

I think a lot of the barrier to entry for Ractors isn't the API for the Ractors themselves but in figuring out how to interact with Ractorized code from code that hasn't been explicitly Ractorized (i.e. is running in the invisible “main” Ractor). To that end I found it easiest to emulate my traditional library API by providing synchronous entry-point methods that make it feel no different to use than any other library despite all the stuff that goes on behind the scenes. The entry methods compose a message to the matcher Ractor then block waiting for a result or a timeout.

I also use Ractors in a more lightweight way in my UUID/GUID library where there's a Ractor serving the incrementing sequence value that serves as a disambiguator for time-based UUIDs in case multiple other Ractors (including invisible “main”) generate two UUIDs with the same timestamp. Speaking of which, I'm going to have to work on this one for Ruby 4.0, because it uses the removed `Ractor.take` method.

reply
ekvintroj
3 hours ago
[-]
My best Christmas gift <3 Love you Ruby.
reply
nish__
4 hours ago
[-]
Ruby::Box looks useful.
reply
shevy-java
2 hours ago
[-]
Right now it is just the foundation I guess. That is, more work to be put on top of it. byroot kind of pointed that out that the proposal reminds him of containers and I think this is the long-term goal eventually, e. g. namespaced isolated containers. At a later time, I think, the syntax for refinements may be simplified and also be integrated into Ruby::Box, since Ruby::Box is kind of a stronger refinement in the long run. But that's my take; ultimately one has to ask matz about the changes. What he did say on the bugtracker was that this is to be considered a low-level API e. g. a foundation work. So things will be put on top of that eventually.
reply
andrewinardeer
3 hours ago
[-]
It truly is Christmas.
reply
desireco42
3 hours ago
[-]
This really makes Christmas festive. I don't think I need new features, but sure love simplicity of 4.0.

I am installing it now. Thank you Matz and team.

reply
ergocoder
2 hours ago
[-]
I haven't looked at Ruby for a long time. I've moved away due to the lack of typing. Any degree of typing would be helpful. Does it support typing yet?
reply
rsanheim
2 hours ago
[-]
_low_type_ is early days still, but I think this approach is clearly the future of ruby typing. If this gets baked into the language for full “compile” time support and minimal performance impact, it will be amazing: https://github.com/low-rb/low_type
reply
ksec
1 hour ago
[-]
It is definitely better than RBS and Sorbet. But unless Github / 37Signals or Shopify decide to use it, it is highly unlikely Ruby Core will consider it.

Out of all three I think Shopify have the highest possibilities. There may be additional usefulness interms of ZJIT.

reply
riffraff
2 hours ago
[-]
There's an official format for defining types in separate files (RBS) and some tooling to type check them (matz doesn't like types next to the source code).

There's a pretty battle tested tool to define inline types as ruby syntax and type check both statically and at runtime[0].

It's still not a particularly nice situation imvho compared to typescript or python, but there's been some movement, and there's a newsletter that follows static typing developments [1] which may give you some insights.

0: https://sorbet.org/

1: https://newsletters.eremin.eu/posts

reply
adamors
2 hours ago
[-]
I’ve used Sorbet on a project for 2 years recently and it honestly was the final nail in the coffin for Ruby for me.

Really rough around the edges, lots of stubs have to be added because support for gems is lackluster but whatever Sorbet generates are hit or miss etc. So you end up writing a lot of hard to understand annotations and/or people get frustrated and try to skip them etc.

Overall a very bad DX, compared to even typed Python. Don’t even want to compare it to TS because then it becomes really unfair.

reply
mrinterweb
2 hours ago
[-]
There is [RBS](https://sorbet.org/) (part of ruby 3) and [sorbet](https://sorbet.org/). To be honest, these aren't widely used as far as I am aware. I don't know if it is runtime overhead, ergonomics, lack of type checking interest in the ruby community or something else. Type enforcement isn't a big part of ruby, and doesn't seem to be gaining much momentum.
reply
vicentereig
28 minutes ago
[-]
I’ve been leaning hard into Sorbet runtime types for DSPy.rb[0] and finding real value. T::Struct at API boundaries, typed props for config, runtime validation where data enters the system.

For generating (with LLMs) API clients and CLIs it’s especially useful—define the shape once, get validation at ingress/egress for free.

Maybe momentum is happening in new projects rather than retrofits? [0] https://oss.vicente.services/dspy.rb

reply
jweir
2 hours ago
[-]
We have been adding Sorbet typing to our Rails application and it is a positive enhancement.

It’s not like Ruby becomes Haskell. But it does provide a good deal of additional saftey, less testing, LSP integration is good, and it is gradual.

There is a performance hit but we found it to be quite small and not an issue.

But there are area of our application that use Grape and it is too meta for Sorbet so we don’t try and use it there.

reply
vicentereig
22 minutes ago
[-]
Same here. T::Struct and T::Enums at API boundaries has been the sweet spot—typed request/response models, runtime validation at ingress/egress.

I’ve been using this pattern for API clients[0] and CLIs[1]: define the shape once with Sorbet, get automatic JSON Schema generation when you need it.

[0] https://github.com/vicentereig/exa-ruby [1] https://github.com/vicentereig/lf-cli

reply
vmware513
2 hours ago
[-]
Unfortunately, the type support is still useless. I abandoned Ruby for the same reason, and it is still relatively slow and eats a lot of memory.
reply
dismalaf
2 hours ago
[-]
It's literally faster than Python but ok.
reply
morcus
2 hours ago
[-]
Is being faster than Python considered to be a notable feature?
reply
gkbrk
1 hour ago
[-]
Python is one the most popular programming languages. Ruby fits into a similar category as Python (high level, interpreted scripting language, very dynamic, has a rich ecosystem with tons of existing code). Being faster than Python makes it more attractive to use, or port Python codebases to.
reply
dismalaf
1 hour ago
[-]
Personally I don't care about speed for this category of language. I just bring it up because Python is one of the most used languages, is even slower, yet that's never held against it. Just seems like a lazy way to dismiss Ruby. Yeah, it's not as fast as C, Go, Rust or Java. Everyone knows and raw speed obviously isn't the point of a dynamic scripting language...
reply
Gigachad
2 hours ago
[-]
There’s projects trying to implement it. But I’ve never seen a project using typed Ruby.

I think most people who cared just moved to typescript.

reply