Ladybird adopts Rust
In the original post I discussed the fact that Ladybird, a full web browser written in C++ had overtaken Servo, a web rendering engine written in Rust, in web platform tests passing. I was surprised by this since my prior would have it that using the Rust language over C++ would be a strong advantage in terms of productivity. I attempted to explain why I might continue to hold that belief in the face of the new evidence given by the Ladybird project overtaking the Servo project. I didn’t fully convince myself, but in February of this year Ladybird announced that they were adopting Rust. This post is an attmepting to understand what’s happening and see if we can draw any conclusions.
WPT scores
As I understand it Ladybird is still ahead of Servo in terms of the web platform testing, try yourself here. Note that the denominators are different, there are a bunch of explanations for this including skipping tests, but in any case both the absolute score and the test percentage are still slightly higher for Ladybird than Servo.
However, that does mask something of a change in the rate of progress. Ladybird’s increase in passing WPT scores has somewhat collapsed, from +13,690 in January to +108 in July. However, that is basically a result of a few things. First of all, the Rust adoption work, most of this is deliberately translating the code as it is in C++ to Rust. Quoting from the announcement (my bold):
The requirement from the start was byte-for-byte identical output from both pipelines. That is in relation to porting
LibJSLadybird’s Javascript engine. Whilst doing this kind of work it is not expected to increase WPT coverage.
Secondly, in June the Ladybird project announced it will not be accepting public pull requests. Though this came in June, presumably it was largely in effect already and presumably came about as a result of a lot of ‘slop pull requests’ generated by LLMs.
Lastly, remember that Ladybird is an entire web browser not just a rendering engine. Looking at the work done in July much of this is concerning the UI and browser profiles, and basically things not covered in the web platform tests.
Revisiting the ten explanations
Recall that each of these were possible explanations, i.e. they were intended as ways in which we could explain the fact that Ladybird was ahead of Servo in WPT without losing our prior belief that strong static guarantees are (generally) more productive. If you re-read the original post you’ll find that I wasn’t terribly convinced by most of these possible explanations. So now I’m simply updating them, does the fact that Ladybird is now adopting Rust change anything for each of these explanations?
1. Rockstars
The idea was that perhaps Andreas Kling, or someone else on the team, is a super productive programmer. The counter argument was that if Andreas Kling or someone else is a super productive programmer why did they choose to work on a C++ codebase? Put another way, if Rust really is more productive, how come the productive programmers are not recognising this?
Well, perhaps they now are and that’s why Ladybird is now slowly coverting code from C++ to Rust. So this explanation gets a bit of a boost on this news but not much of one.
2. More contributors to Ladybird
Was never very convincing and we may get some more clarity on this in the future since the Ladybird team has chosen to exclude public pull requests. The move to Rust doesn’t change this explanation.
3. Focus on WPT
I think there is some evidence for this, and it’s now possibly running in the other direction (or has been for much of this year) and that has allowed Servo to catch up somewhat. So I do not think the adoption of Rust changes how appealing this explanation is, but some of the other developments since I wrote the original post do.
4. Luck in some form
Was always a very vague explanation and it remains so. I think my own response still stands, that if this was the explanation it suggests any productivity improvements from language choice are not that large.
5. Some kind of better architecture
I originally supposed that perhaps Ladybird had a better architecture than Servo. However, that raises a bunch of other questions, similar to number 1. If the better architecture was through better expertise why is that same expertise not choosing Rust? So 5. was something a combination of 1 and 4, either the better architecture is because they have better expertise or luck. In the case of better expertise you have to ask why that better expertise chose C++ (and was wrong to do so).
Now as it turns out, I think this may actually be a large part of the answer. In their Tech over Tea podcast episode Andreas Kling states:
Lady Bird is a very single-threaded browser, which is not great in 2026 to be single-threaded, especially given the incredibly huge workloads browsers tend to receive
So he is basically saying that the architecture of Ladybird is single-threaded. One could think of this as a ‘better architecture’ for making progress quickly, particularly on correctness and web platform tests. You could also think of it as taking on some technical debt, you realise that at some point you’re going to need to make your web browser multi-threaded, but you want to make progress now. That could explain the hithertofore progress Ladybird as made on the web platform tests ahead of Servo. Now that the team has chosen to pay down some of that technical debt they have made the decision to use Rust (part of the reason for adopting Rust is that they see it as a better language for multi-threaded programming). So this explanation is now more appealing than it was before, albeit ‘better architecture’ is now more nuanced.
6. C++ lends itself to a better architecture than Rust
This explanation is somewhat undercut by the Ladybird team choosing to move to Rust. But I think it undercuts it in a way that is consistent still with stronger static guarantees being more productive. C++ lended itself to a single-threaded architecture that was more productive for the goal of making progress on features and web platform tests. But less so for actually building a usable web browser.
7. Web rendering is a rare case
The idea was that web rendering was a rare case where C++ was more productive than Rust. This wasn’t likely at the time and was included more in an attempt at being exhaustive. This explanation is also undercut in a way that supports the priors by the fact that now both teams have concluded otherwise.
8. Faster but buggier
I posited that it was possible the Ladybird code was actually buggier since neither codebase had really seen a lot of real world use. I think this is still quite hard to judge. I suppose the Ladybird team choosing to adopt Rust is weak evidence for this, in a way that is a bit similar to the bun rewrite from zig to Rust. In that post the Bun team show a sample of the kind of memory bugs they were seeing in the Zig codebase and concluded:
We could have kept fixing these kinds of bugs one-off in perpetuity, but we owe it to our users counting on us to do better than that, and systematically prevent these kinds of bugs from recurring.
9. Compile times
I supposed that there is some other factor that made Rust less productive than C++. As an example I noted that the Rust compiler is famously quite slow. Perhaps all other things being equal strong static guarantees are indeed more productive. However, a slow compiler may be more of a productivity killer (perhaps it interrupts a programmer’s “flow” state). Perhaps the slow compiler hit more than cancels out the productivity gains from the strong static guarantees. I noted at the time, that in that case the statement ‘strong static guarantees are more productive’ is somewhat more nuanced.
In any case, the Ladybird developers have adopted Rust in full knowledge of how slow the compiler can be. So this is weak evidence against this explanation.
10. Mozilla pull the plug on Servo funding
I think this explanation is indeed a part of the whole explanation. In the piece I wondered why Mozilla would do that if they were seeing massive productivity gains. First of all, layoffs at Mozilla that included Servo developers were part of a company wide restructuring and a financial rather than technical decision. I still think there is some grain of truth here, that it places something of a ceiling on just how much more productive a language with strong static guarantees can be. After 8 years if they were seeing 10x productivity gains then I think those Rust developers on Servo might have been redeployed (to places in Firefox where Rust is being used). Or generally speaking more attempts to adopt Rust throughout the Mozilla codebase. However, it’s not as if Mozilla have abandoned Rust.
Ladybird’s July 2026 newsletter
A separate section here for Ladybird’s July 2026 newsletter just because it contained two very interesting parts for this discussion. The first is that ArrayBuffer was the source of most exploits:
Almost every exploit anyone has demonstrated against Ladybird to date has used ArrayBuffer. Once you got control of its data pointer, you could use it to read/write all memory in the process. It was an amazingly convenient exploit primitive!
The fix is to cage ArrayBuffer and WebAssembly memories from the rest of the process memory:
This month we’ve made it a lot less convenient: ArrayBuffer contents and WebAssembly memories now live inside one reserved 4 TiB region of address space, and every access is forced to land inside it (#10391). An out-of-bounds access is still a bug, and it can still reach other buffers inside the cage, but it can no longer reach the objects, stacks and code outside it.
Presumably this wouldn’t have been a problem in a Rust codebase and I think this echoes somewhat the point, referenced above, made by the Bun developers, from the section “Why Rust?”:
A large percentage of bugs from that list are use-after-free, double-free, and “forgot to free” in an error path. In safe Rust, these are compiler errors and RAII-like automatic cleanup with Drop. Compiler errors are a better feedback loop than a style guide.
Statically typed DSL in Ladybird
This is just interesting. The inner-loop of the Ladybird’s Javascript engine is a low-level interpreter. There is a handler for each bytecode instruction. This was mostly written in an assembly dialact, which referred to symbolic variables rather than machine registers which then underwent register allocation (assigning symbolic names to actual machine registers). Anyway, it’s worth reading the section itself from the newsletter but the example handler given is:
handler BitwiseXor
temp lhs, rhs, lhs_int, rhs_int, dst
load_binary_operands lhs, rhs
coerce_to_int32s lhs, rhs, lhs_int, rhs_int, .slow
xor lhs_int, rhs_int
box_int32 dst, lhs_int
store_operand m_dst, dst
dispatch_next
.slow: @cold
call_slow_path asm_slow_path_bitwise_xor
end
But nothing knew that
lhs_intholds an integer, nothing checked that a value was written before it was read, and the interesting decisions were buried inside macros likecoerce_to_int32.
So they built their own typed version of the assembly to write the interpeter in, that looks like:
handler BitwiseXor(dst: out Operand, lhs: in Operand, rhs: in Operand) {
let lhs = load(lhs);
let rhs = load(rhs);
let slow = || @cold {
call_binary_slow_path(bitwise_xor_values, dst, lhs, rhs);
};
let non_int32 = || @cold {
guard let lhs_integer = numeric_value_to_i32(lhs) else slow;
guard let rhs_integer = numeric_value_to_i32(rhs) else slow;
xor(lhs_integer, rhs_integer);
store(dst, box_i32(lhs_integer));
dispatch_next;
};
guard let Value<i32>(lhs_integer) = lhs else non_int32;
guard let Value<i32>(rhs_integer) = rhs else non_int32;
xor(lhs_integer, rhs_integer);
store(dst, box_i32(lhs_integer));
dispatch_next;
}
Anyway they talk about how the gains mostly come after switching to this dialect because now you could specialise parts based on the types in those symbolic registers. Again, it’s worth reading the section from the July newsletter. But the point is, even at this low-level the team have found that strong static typing gives some benefits and here the benefits they extoll are performance benefits, even if I suspect there are some benefits to maintainbility as well.
Conclusion - My speculative explanation
Taking into account what we’ve learnt in the last year or so of Ladybird’s development, and in particular its adoption of Rust; my current speculative explanation for why Ladybird was able to make more progress than Servo, particularly on web platform test coverage, goes something like the following:
- Looser languages are fine (or even better) for programming in the small
- Strong static guarantees are better for programming in the large
- Parallelism is a way in which programming in the large bites. Large systems are difficult because when you touch one part of the code it can have an affect on other, possibly far away, parts of the code. When concurrency is involved it’s even more likely that a change somewhere can have an affect somewhere else. If you like, adding concurrency increases the effective size of the codebase.
- Because Ladybird was single-threaded the effective size of the codebase was smaller than Servo’s and for that reason they were able to make more progress, at least by the measure of web platform tests. Even if the choice of C++ and lack of static guarantees was a productivity drag.
But I will note that I still think the whole thing puts something of a cap on the productivity gains from strong static guarantees. If the productivity gains were really 10x, wouldn’t that be obvious? Is it obvious?