I recently rewrote one of my rails apps in rust. Used Claude 4.5 Opus heavily and it was very fast.
One thing that's struck me with the new code is that's its so easy to follow compared to rails. It's like two different extremes on the implicit-explicit spectrum. Yet it's not like I have tons more boilerplate code now, I think I have maybe 10 or 20% more SLOC than before.
I'll probably do this with my other rails apps as well.
Non-ruby dev here. Can someone explain the side exit thing for me?
> This meant that the code we were running had to continue to have the same preconditions (expected types, no method redefinitions, etc) or the JIT would safely abort. Now, we can side-exit and use this feature liberally.
> For example, we gracefully handle the phase transition from integer to string; a guard instruction fails and transfers control to the interpreter.
> (example showing add of two strings omitted)
What is the difference between the JIT safely aborting and the JIT returning control to the interpreter? Or does the JIT abort mean the entire app aborts (i.e. I presumed JIT aborting means continuing on the interpreter anyway?)
(Also, why would you want the code that uses the incorrect types to succeed? Isn’t abort of the whole unit of execution the right answer here, anyway?)
Dynamic languages will allow a range of types through functions. JITs add tracing and attempt to specialize the functions based on the observed types at runtime. It is possible that later on, the function is called with different types than what the JIT observed and compiled code for. To handle this, JITs will have stubs and guards. You check the observed type at runtime before calling the JITted code. If the type does not match, you would call a stub to generate the correct machine code, or you could just call into the interpreter slow path.
An example might be the plus operator. Many languages will allow integers, floats, strings and more on either side of the operator. The JIT likely will see mostly integers and optimize the functions call for integer math. If later you call the plus operator with two Point classes, then you would fall back to the interpreter.
In this case, we used to abort (i.e. abort(); intentionally crash the entire process) but now we jump into the interpreter to handle the dynamic behavior.
If someone writes dynamic ruby code to add two objects, it should succeed in both integer and string cases. The JIT just wants to optimize whatever the common case is.
ZJIT exists because it's a more traditional design and there's hope more people will have a easier time contributing[0]. Given that, it seems YJIT will become unnecessary if ZJIT succeeds.
Earnestly: why are you annoyed? I tried to make it clear that you don't have to make any changes. If you want, you can try ZJIT (which should not be anything other than a one character change), but you don't have to.
I recently rewrote one of my rails apps in rust. Used Claude 4.5 Opus heavily and it was very fast.
One thing that's struck me with the new code is that's its so easy to follow compared to rails. It's like two different extremes on the implicit-explicit spectrum. Yet it's not like I have tons more boilerplate code now, I think I have maybe 10 or 20% more SLOC than before.
I'll probably do this with my other rails apps as well.
Was your app converted to use some Rust framework, or just Rust?
I use Axum+SQLx and for html templates I use Maud. The plan is to move to Dioxus as a step 2
Non-ruby dev here. Can someone explain the side exit thing for me?
> This meant that the code we were running had to continue to have the same preconditions (expected types, no method redefinitions, etc) or the JIT would safely abort. Now, we can side-exit and use this feature liberally.
> For example, we gracefully handle the phase transition from integer to string; a guard instruction fails and transfers control to the interpreter.
> (example showing add of two strings omitted)
What is the difference between the JIT safely aborting and the JIT returning control to the interpreter? Or does the JIT abort mean the entire app aborts (i.e. I presumed JIT aborting means continuing on the interpreter anyway?)
(Also, why would you want the code that uses the incorrect types to succeed? Isn’t abort of the whole unit of execution the right answer here, anyway?)
Dynamic languages will allow a range of types through functions. JITs add tracing and attempt to specialize the functions based on the observed types at runtime. It is possible that later on, the function is called with different types than what the JIT observed and compiled code for. To handle this, JITs will have stubs and guards. You check the observed type at runtime before calling the JITted code. If the type does not match, you would call a stub to generate the correct machine code, or you could just call into the interpreter slow path.
An example might be the plus operator. Many languages will allow integers, floats, strings and more on either side of the operator. The JIT likely will see mostly integers and optimize the functions call for integer math. If later you call the plus operator with two Point classes, then you would fall back to the interpreter.
In this case, we used to abort (i.e. abort(); intentionally crash the entire process) but now we jump into the interpreter to handle the dynamic behavior.
If someone writes dynamic ruby code to add two objects, it should succeed in both integer and string cases. The JIT just wants to optimize whatever the common case is.
It would be useful to explain why ZJIT exists given that there's already YJIT.
Also, what's the long-term plan for YJIT.
ZJIT exists because it's a more traditional design and there's hope more people will have a easier time contributing[0]. Given that, it seems YJIT will become unnecessary if ZJIT succeeds.
0: https://railsatscale.com/2025-05-14-merge-zjit/
I just upgraded my prod apps to run on YJIT so I'm annoyed at this announcement. Feels like javascript-esque runtime churn
Earnestly: why are you annoyed? I tried to make it clear that you don't have to make any changes. If you want, you can try ZJIT (which should not be anything other than a one character change), but you don't have to.