Port React Compiler to Rust(github.com) |
Port React Compiler to Rust(github.com) |
I support wholeheartedly the move to AOT-compiled languages but it looks like paying off the cognitive debt is going to be brutal on whichever team gets to maintain it in the long run.
Just creating permanent cognitive debt for everyone else with no-one else understanding what the code is doing.
Sure, but what machine model do the public contract docs and RFCs target ? While the specific coding language is a detail, programming languages target a precise machine model. There is no machine model for english prompts and spec docs. So expect fuzziness - he-said-she-said bugs in your code - things that the LLM made up because the RFCs were not precise. And by the time you add precision to the specs, it has morphed into a new programming language with a machine model. In this usecase, since it is a port, the impact is mitigated because it is a clone of some existing functionality.
Very curious to see how these rewrites play out. Is the LLM foundation solid enough to build upon and iterate on? Or does this cause projects to become unmaintainable because no person understands the implementation anymore?
Had it convert it in this and the stock version, compare the output, fix and repeat.
> This is an experimental, work-in-progress port of React Compiler to Rust. ...
And then gets merged.
So after the craziness to use node on the backend, we are back to using compiled languages to compile Javascript assets and Web resources, just like Java and .NET were doing in the 2000's, however since it is Go and Rust it is cool, not the boring languages grandpas were using on their heyday.
I'm not sad to see the pendulum swing away from "javascript everywhere" though.
Likewise, .NET has had NGEN since day one even if with limitations, and after several detours in AOT approaches, NativeAOT is in the package and cross platform, thus also a moot point in 2026.
But again, they aren't cool for the kids today.
> And then gets merged.
The initial commit attached to that message was the start of the experiment. Are you reading the first message as if it was the last?
It was merged after 3 months and a huge number of incremental commits. The code that was merged was completely different than the code used to open the experimental WIP PR
Why is it a criticism that they started as a work in progress experiment? That’s how experiments work. It’s normal to push code as a work in progress PR and then add to it over time.
I suspect this won't have as big of a shit storm as the Bun port in part just due to the input/output nature of the React compiler.
That said, while I use React still, I still have never tried the React compiler... So I have no idea how important this is. But you know, very few people are ever upset over faster iteration cycles or CI builds.
Only if the existing tests were actually useful in what they tested and covered.
It is possible to have thousands of tests and a lot of blind spots, but it's easier to get a grip on that using instrumentation like code coverage and indeed by using LLMs to prod at edge cases.
I am OK with trusting that it is likely the React team understands how to rigorously test based on how well-tested React itself is.
The real boost to AI, is it can do a lot of things pretty easily, that you might not on your own... really thorough test suites, or even standing up a demo application just to test a single complex component. The latter I've done a few times now.
Rust itself seems to be very well supported by the coding models and is a pretty sane language to use. I cannot really comment on Go, as I've been using a lot more Rust as I have been targeting stand alone and wasm targets respectively, the latter really isn't a good structure for Go. I can say that C# is a really mixed bag, and would assume Java is similar.
As codebases grow it only gets worse, as more of your mind is filled with building a mental model of the type system.
That’s why you end up seeing crazy defensive coding practices in these languages. In PHP, I often see isset and instanceof spammed everywhere because nobody can garauntee that thing X is actually X all the time.
The AI can iterate faster in Python too, guaranteed, because you don’t have any compile time.
Also, this is how AIs already get stuff done: they write SCRIPTS to perform tasks in a sandbox with tool calls. This is the future, dynamic languages, not using static languages.
Haven't heard about since ages ago when it was extremely slow
It's brilliant: all useMemo and useCallback can be removed and you get the same runtime performance and then some, at the cost of only a slight increase in code size.
A small downside at the moment is the build time. This change will hopefully help address that because it will no longer depend on babel.
I would be interest to hear how it worked out for you.
I also took a step to clean out all memo, useMemo, useCallback across the app and so far have not run into any issues from doing this.
Not sure how they relate to each other.
Just cause it isn't used for webshit doesn't mean "approximately no one" has heard of it.
Lean is pretty much the most popular language mathematicians use today for computer-assisted proofs. More mature audiences may know about Rocq, Isabelle, etc., but Lean was already popular enough for a few people I know to have written their PhD theses on it about a decade ago.
I think GP is joking about a port to Lean because that would at least produce a formally verifiable output.
My first programming interview my interviewer was like what the heck are you doing with D? And he noted he has a room full of devs where nobody knows what D is.
Seems like they kind of did that? The thread seems like people already were waiting on this, so that's positive.
It seems backwards that they are freezing the Babel AST into the interoperability contract and only using the more efficient native representations in an isolated fashion -- shouldn't it be the other way around?
> Note that the conversion from any AST into our HIR is complex, and we can only maintain one version. Hence we've aligned on using a Babel-like AST as our public API. Another key point is that we don't yet implement our own scope analysis (since the TS version of the compiler relied on Babel's scope analysis), so for now we require that the scope data be serialized. It's a denormalized graph, and some metadata has to be stored to associate nodes with scopes. We're open to feedback about the AST and scope representation - we iterated a bit just to get things to work, but it can be more optimal.
Whats funny is I had been using Rust more with Claude because of this, and before Anthropic did their rewrite of Bun I tried a Rust rewrite of a C# project I had laying around (.NET 3.5 from back in 2008) it wasnt perfect but its nearly there now, mostly for fun, and I did it because for a little while I realized LLMs can be useful for more serious refactors, and figured it might be good for a language rewrite. Sure enough.
I think rewriting tooling that takes text and transforms it in a language like Rust is fine for JS projects, so is Go, which is why TypeScript is migrating to Go. The “free” optimized speed increases are worth it, at the temporary cost of dealing with the migration for a year or two, which with LLMs trims down the initial work into a week effort it seems? Wild.
I have noticed, in general, LLMs tend to "fix" problems by shoving them under the rug (like adding a cast) or writing a super-local "fix" instead of taking the time to understand the deeper problem or structure.
In Rust, when you get stuck in a complicated borrow-checking problem, Rust people will tell you it's a Good Thing(tm) because it forces you to think about the higher level architecture of your code. An LLM, on the other hand, might bash its head a couple of times trying to "fix" the problem, and then just throw in a Refcell (or other workaround), see that it compiles, and call it a day.
Refcells "move borrow checking to runtime", meaning that the code will compile, and will crash at runtime if the object is tried to be accessed from two different places at the same time. In most "normal" programming languages it's not an issue -- but it's a crash in Rust.
Now, maybe the models have gotten better, and maybe you can get around this problem by using a good system prompt/"tools" and a good testing methodology. What I am saying however is that you shouldn't automatically take "rewritten to Rust by AI" at face value of it being good Rust code, or a testimonial of "Rust and AI being a good match". (Go is better I think)
Memory safety is just a tiny part of over all security. If a LLM can transcode correctly, then it should also output 100% correct C code.
On the other hand, If a LLM cannot correctly transcode, then using Rust may just make the bug soundless, because the language runtime/code-gen "avoided" usual punishments that might make the bug (and bug report) obvious.
I get the opposite impression. If they aren't confident enough to write it by hand, I think it means the language is either hard to write, hard to read, or both. And by delegating to a LLM, there's no "barrier of entry" being lifted, it's just saving you typing time (like it would if it was being translated to C or Kotlin). If they actually decide to "focus on all the other programming logic" and they aren't just vibe coding this, they'll still need to be able to understand and reason about the code.
Does that actually happen?
I think you actually meant "lower the barrier" (which would make it easier to pass the barrier, while lifting it makes it harder)?
Also I think this is all temporary, just like devs still did not believe on optimising compilers and checked each line of generated Assembly code, during the 1960's.
Eventually it will be natural language, and all languages will be kind of irrelevant.
You may be thinking about React itself, not the new compiler? I'm sure there must be some flow in there still from back in the day.
I just checked out Flow and woah. First-class syntax sugar for React. Maybe cool. (If it doesn't break catastrophically in a sane build system like Vite)
I like Lean (and more generally dependent types) but ffs Lean has a very, very small userbase for a project like this. GGP would have to really justifyv the benefits for such a switch.
No, it's a pretty massive part with disproportionate severity.
> If a LLM can transcode correctly, then it should also output 100% correct C code.
Translating code seems to largely rely on having a strong suite of existing tests, not on ability to code correctly.
It's unclear if LLMs are great at writing safe C code, it's much clearer that they can meet targets with external feedback properties like "test passes/fails".
> On the other hand, If a LLM cannot correctly transcode, then using Rust may just make the bug soundless, because the language runtime/code-gen "avoided" usual punishments that might make the bug (and bug report) obvious.
This is very unclear to me.
70%[1][2] is tiny?
[1]: https://www.zdnet.com/article/microsoft-70-percent-of-all-se... [2]: https://www.chromium.org/Home/chromium-security/memory-safet...
> On the other hand, If a LLM cannot correctly transcode, then using Rust may just make the bug soundless, because the language runtime/code-gen "avoided" usual punishments that might make the bug (and bug report) obvious.
Isn't it the other way around? Rust guarantees lack of undefined behavior in safe code. If you have undefined behavior in your code your bug might become a heisenbug, or make the rest of your program behave weird, or the bug might simply be dormant until a very specific situation occurs (i.e. be "soundless" as you say).
If you're going to automatically translate your code from one language to another then a memory-safe target language (whether it's Rust, Java, C# or something else) is the only sane, reasonable choice. And if you want C or C++-like performance (i.e. you want to maximize performance) then you're pretty much left with Rust on the table.
OR, the OS might kick in and throw a segmentation fault etc, often with some information associated to it.
Again, if a LLM can output 100% correct code, no bug of whatever kind should exist. Seeing a segfault could just invalidate that assumption completely and definitively. That's the point.
> Rust guarantees lack of undefined behavior in safe code
And that don't guarantee heisenbug-free, that just means your heisenbug was fully checked by Rust compiler and is now managed by the language runtime/facilities.
So, now instead of a crashed program and a "sever" DoS vulnerably, you got a disconnected user every time they trigger the bug. The user might assume it's the network, so does your logging stack. After a few times, the user starts bitching about your stupid network, and left for your competitor's product, while you busily trying to figure out why the network suddenly ain't as good as it used to.
> 70%[1][2] is tiny?
It really depends on how they define what counts as "vulnerably", or in Chrome's case, "'high severity' security bugs" which is very specific. Microsoft probably have many decades-old code written before the invention of better checkers, that contributed to the problem.
Sure, in the same way that foundations are just a tiny part of house construction.
Foundations come first, it's great if you've got more on top, I recommend it actually, but if you don't have the foundations it's not even worth having the discussion about what else you built, it's all worthless.
So while LLMs are good at writing walls of code, they do not produce good code, just good enough and sometimes it is wrong (here is where Rust can help a bit by checking that the program is sound, but for the most part you should also validate the logic).
The dream language for LLMs would be one that has some kind of proving that function inputs/outputs are what you expect (I think it's called proof theory, but it's not my area of expertise, so I could be wrong), you kind of can emulate this with new types[0].
[0] https://doc.rust-lang.org/rust-by-example/generics/new_types...
Well LLM's cannot transcode perfectly correctly, so the fact that Rust has lots of static checking is really important. Not just for memory safety - Rust helps with many other classes of bugs too.
> then using Rust may just make the bug soundless, because the language runtime/code-gen "avoided" usual punishments that might make the bug (and bug report) obvious.
I think what you're saying here is that LLM's often cheat to solve the immediate error, e.g. by using `unsafe` where you really shouldn't, or just making a test not test anything. That's definitely possible.
An LLM can't (currently) transcode correctly in a vacuum. It needs tight guardrails to keep it on the straight-and-narrow (such as an existing conformance test suite that is extremely comprehensive).
The value of transcoding to Rust specifically is that the compiler gives you a pretty substantial set of guardrails "for free" - in a C port, your conformance test suite would also need to test every aspect of memory safety and fearless concurrency...
TypeScript is memory-safe, but you can't really control where the memory comes from. In Rust you have structs, traits, references and all sorts of things to control both your memory usage and your memory efficiency, and you just don't really get that in TypeScript. Plus, in Rust it's a lot easier to utilize multithreading -- JS is notoriously tedious to parallelize (message-passing between JS workers is a bit annoying compared to structured concurrency in Rust)
- Lints [1] that flag code that cannot be (correctly) optimised. Usually this is obscure code that is too smart for its own good. But the compiler leaves it alone and flags it for review, so most things just keep working.
- Lints that flag code that violate the rules of hooks. These rules became more critical to follow: failure to do so may break rendering. But non-compliant code can be easily be excluded from compilation [2], so you do not have to fix everything at once.
- Popular libraries that are not compatible (yet) are flagged and excluded automatically [3].
The compiler is better than manual memoization, because 1) it is hard not to forget memoizations, and 2) the compiler's output memoizes more granularly than manual memoization realistically could.
I have not found performance regressions. Not saying they're not possible; but we haven't encountered them.
We have a very performance-sensitive project that used preact (chosen for performance) via its compatibility layer, that we switched to React + React compiler. Performance is noticeably better than with preact. Whereas previously the React-only version was incredibly slow even with carefully placed memoizations, because they were very hard to get right.
[1]: https://react.dev/learn/react-compiler/installation#eslint-i...
[2]: https://react.dev/learn/react-compiler/incremental-adoption
[3]: https://react.dev/reference/eslint-plugin-react-hooks/lints/...
const nestedDependency = { a: { b: { c: 'c' } } }
useMemo(() => nestedDependency.a.b.c, [nestedDependency])
vs
useMemo(() => nestedDependency.a.b.c, [nestedDependency.a.b.c])
neither triggers react hook lint warnings, although I guess this is more relevant to useEffect than memoization.
The current developers surely are more familiar with the Babel representation than OXC, so why switch?
This is just how software development for a large public project goes. If you want to land big changes in a finite amount of time, you take the path that breaks the least number of things along the way.
Once the whole ecosystem is rust-based, they can always trim out some of the redundant intermediate representations for performance gains.
Of course that doesn't mean that there are no businesses logic bugs.
And yes, both languages are considered "not cool" since they lean heavily into code structuring that the industry has largely moved past.
AOT .NET is an official first party distribution target that in my experience is on par with building a Go binary
Don't knock it till you try it
I don't see what Python has to do with anything.
And I do still think there is an advantage for this kind of tooling to choose languages that are designed to compile to a native binary. At best Java and C# are equivalent to Go and Rust here, maybe they are less "cool" but I don't see any reason why this rewrite would be better if it were to Java than Rust.
> the borrow checker woes can be offloaded to the model, you focus on all the other programming logic
Then the person you responded to asked if "you" really do "focus on all the other programming logic", given that "the model" deals with the borrow checker. In other words, they're asking about the work the actual human is doing at that point. You then replied talking about the model again and token budgets. In effect, you brushed the question under the rug and imply that _everything_ is offloaded to the model, no focus by the human required.
In fact both languages conveniently map to COM object model, and Microsoft is putting that to good effect.
Than we have Swift and Kotlin, plenty of OOP on those Apple and Google frameworks.
Zig, Odin, remain to be seen how industry relevant they will be, and yet examples of structs with function pointers abound among their projects, C OOP style.
So....
Usually only looked in high regard when the content supports the message, proudly ignored otherwise.