Upstream Rust maintenance report (August-September 2026)
As noted in my previous report, I am currently working on the open source Rust toolchain as a Sovereign Tech Fellow. Every two months, I’m putting out a report of my open source work done in that period. This is the second installment of this series. Same as the last time, I’ll try to pick a few highlights, summarize the rest of the stuff that I worked on, and also provide contribution statistics and a raw list of opened PRs.
This post details my open source Rust work done in August and September 2026.
Here’s an index for simpler navigation:
Reducing target directory size
I already wrote in my previous report, and also earlier on this blog, about the initiative to reduce the size of the target directory by removing duplicated metadata of Rust crates, which can make it smaller in real-world projects by 5-35%, so it can be quite significant.
Last time, I noted that we are waiting for Cargo’s new build dir layout nightly experiment to conclude, before we enable yet another experiment on nightly by default. This happened during August, so I enabled the usage of -Zembed-metadata=no on the nightly channel by default, and announced it on the Rust blog post.
The good news is that we haven’t really heard any large complaints or issues about it since then, and several build systems already successfully updated to the new mechanism, where the metadata is kept only in .rmeta files, which then have to be passed explicitly to rustc via the --extern flag.
Thanks to that experiment, I discussed moving forward with the Cargo team, and opened a stabilization report for the compiler side of the feature (turning the unstable -Zembed-metadata flag into a stable -Cembed-metadata flag), which is now in FCP vote :tada:. Soon after, Weihang Lo opened a separate Cargo stabilization report for the Cargo side of the feature (passing -Cembed-metadata=no to rustc by default). There are some remaining questions about how does this affect Cargo’s stability story about .rlib, .dylib and.rmeta` artifacts, but I think that this should not block the compiler side of the stabilization.
Since Cargo is already using the unstable -Zembed-metadata flag by default on nightly, and Rust’s own build system is also making use of that flag, simply moving -Zembed-metadata to -Cembed-metadata, which is what we would normally do, would immediately break nightly users, unless we managed to synchronize that change across both rustc and Cargo atomically, which is not trivial at the moment. The plan is thus to keep supporting both -Zembed-metadata and -Cembed-metadata for some time, to allow users to migrate to the stable version of the flag, and then finally remove the unstable flag.
This is quite exciting, because it looks like we might be finally close to removing the duplication of Rust metadata on disk, which existed in Rust for almost 10 years, and was unnecessarily inflating the size of the target directory.
By the way, it seems that a Project Goal focused on reducing the size of the target directory further is in the works. Maybe don’t go buying another disk drive just yet!
Compile time improvements
Same as in the previous period, I tried to spend some time on improving the performance of the Rust compiler, though this time I don’t have that much to show for it. My experiments were mostly focused on trying to apply arena allocation to various parts of the compiler. I must admit that I did not achieve much success, partly because of my own unfamiliarity with arenas (though I learned a lot during my experiments!), partly due to the way the compiler codebase is structured, and partly because I now think that the design of Rust is sort of hostile towards arena allocation. Though with the upcoming stabilization of the Allocator trait, it should become better.
Apart from the high-level areas that I describe below, I also worked on some random small performance improvements:
- Added a fast path to string escaping in the compiler in rust#160453 (thanks @matthieu-m for the suggestion). Surprisingly, escaping strings can be quite hot, and the standard library’s escaping code is currently not very fast.
- Removed an unnecessary
.clone()call from the macro expansion system in rust#162004. It would be nice if Clippy’s redundant_clone lint could catch this, but unfortunately it is currently not very smart. - Resolved a performance regression from a previously merged PR in rust#162371.
- Applied LTO to Cranelift in rust#163412. This didn’t help all that much. I also want to try to apply PGO to Cranelift, which I hope will help more.
- Tried to reduce the size of macro-generated code in the
tracingcrate in tracing#3603, because I saw a lot of unnecessary generated code duplication there in bors. But it seems thattracinghas been unmaintained for some time, so I’m not sure if anyone will take a look at it.
Trying to optimize the Rust parser
I like reading about how other programming languages and communities implement their compilers. In particular, I always enjoy seeing the tricks that the Zig compiler pulls off. It implements parsing very efficiently, using a Data Oriented Design/Entity Component System approach, and I find that quite cool.
I wanted to try using a similar approach for tokenization and parsing also in the Rust compiler. Now, it should be noted that parsing Zig or C is a very different discipline than parsing Rust. It’s not necessarily that Rust’s grammar is that complicated to parse, but some of the language’s design, where parsing is interwoven with macro expansion and name resolution, and its focus on providing great diagnostics, makes its parsing logic much more complicated than one might think.
Today, the Rust lexer and parser represent tokens using essentially a tree representation. Token trees are stored in a Vec, where each tree can either be a leaf token (like +) or a delimited group of nested trees (like (1 + 2)), which is stored in a separate Vec. In other words, everytime the parser encounters parentheses, braces or brackets in a Rust file, it will allocate a new Vec on the heap. As you might imagine, this is not terribly efficient, neither time-wise, nor memory-wise.
I tried to change that to use a flat representation, where all tokens, including delimited sequences, are stored in one single Vec, to avoid all those tiny allocations. Changing the lexer was very simple, and locally produced ~30% wins in terms of lexing performance, which was nice. However, moving this change up to the parser, macro expansion and AST handling was quite… involved. It was not really feasible to change the representation everywhere at once, because there is a lot of code that accesses the TokenStream type, which abstracts the Vec of token trees. So I had to create a second implementation on the side (using the flat Vec of tokens), reimplement all the old functionality using the flat token structure and implement conversion functions in both directions (from flat to nested trees and from nested to flat trees). Then I had to incrementally migrate usages of TokenStream in the compiler to the new representation, by moving the conversions to higher and higher-level call-sites, until all conversions were gone.
After a lot of work, I managed to get quite far in rust#159378. However, the performance results are not very impressive so far: the compiler is actually slower with the flat token representation. The best result I achieved was this, but then once I started moving more stuff into the new representation, it actually regressed even more. The reason is mostly macro expansion. The nested tree representation with every delimited group being a separate allocation is actually quite useful when you want to do a lot of modifications of the parsed list of tokens, which is exactly what happens during the expansion of both declarative and proc macros, and also in a few other parts of the parser, usually around attributes, where we currently do a bunch of in-place modifications of the input token stream (which is kind of terrifying, and a hack).
I think that it should be possible to either modify that code to be more friendly to the flat representation, or somehow use a hybrid representation that would combine the performance benefits of the flat representation with the ability to be easily modified of the nested representation. Not sure how that would look like though.
Some further performance wins might also be gained by:
- Reducing the size of individual tokens. Each token has ~40B at the moment, which is kinda ludicrous.
- Using a chunked
Vec, so that appending to the end of the flat list doesn’t require reallocating and copying the whole token list when theVecruns out of memory. - Migrating
AttrTokenStreamto the flat representation too, so that we don’t have to convert between the flat and separately allocated token trees when dealing with attributes.
I think that this might be a situation where the performance results keep being red until the last bottleneck/conversion point is removed, and at that point it could jump to being green a lot. But so far it looks like getting to that point is quite difficult :)
While staring into the token parsing code for many weeks, I noticed that there are other sources of inefficiencies. For example, the parser snapshots its state before parsing certain constructs, so that it can reparse some parts of the code lazily at a later stage. This snapshotting happens quite often (on bors I measured over 250 thousand snapshots being made), and it is a bit expensive. That might sound surprising; isn’t the parser state essentially just a single number with a position pointing into an input string? Well, I wish :) The issue is that because of how the delimited groups are represented, we actually have to remember a stack of currently active parent delimited groups, so that when we encounter the end of a delimited group, we know where to backtrack in the parser. As an example, when we are parsing (1 + array[0] + 2), and the parser is at , where the [0] group ends, it has to be able to go back to its parent delimited group ((1 + ...)), so that it can access its data.
What makes this a bit infuriating is that most times, I suspect that we don’t even end up using most of the parents at all, but we still pay the cost for cloning the whole parent stack. This is kinda stupid, so I tried to figure out some ways of not cloning a Vec of several items everytime a snapshot is made in rust#162593. I tried:
- Storing intrusive pointers in the delimited group stack items, so that snapshotting only clones at most one parent item, and I can find its parent by following the pointer. But this trades snapshotting performance for adding yet another allocation when encountering each delimited group (so that we can store the parent somewhere), which resulted in performance regressions.
- Using immutable datastructures (using crates like
im) to reduce the overhead of cloning. This also resulted in performance regressions. - I considered using an
Arcinside the delimited groups to remember their parents, but I quickly ditched this idea, because it would be a lot of work to make this work, and would likely run into reference cycles (though that would be resolvable via usingWeakpointers), and would inflate the size of each token tree even more. - Only clone the immediate parent, not the whole parent stack. I suspect that this is enough, because I don’t think that we ever need to recurse back into grandparents when restoring the snapshotted parser, and it produces relatively nice performance results. The problem is that so far I haven’t been able to convince myself that this works in 100% of the cases or find someone who would be confident enough about how the parser works to confirm this for me.
In the flat representation, I removed the need to remember the parent stack by storing an index into the start of a parent into each delimited group. That makes snapshot cloning essentially free. But everything is a trade-off, because this increases the size of each token, which has a negative effect on performance.
Anyway, I might return to this work sometime later, though rust#159378 will require a lot of conflict fixes, because it modifies many files in the compiler.
Making Polonius faster
Since August, the Rust compiler uses the Polonius borrow checker implementation on the nightly channel by default :tada: This was a long time coming and I’m very excited by it. Compared to the previous borrow checker implementation (NLL), it is currently slower in some cases though. When it originally landed at the start of August, check builds of serde had a ~15% instruction count regression (end-to-end) vs NLL.
Since then, Jack Huey and Rémy Rakic, who drive the whole Polonius effort, did a lot of great work to speed up Polonius. Now, the regression of Polonius vs NLL on serde is just around ~3-5%. Since this is an end-to-end measurement, and borrow checking is usually a relatively modest fraction of the work performed by the compiler, this means that Polonius itself likely became several times faster.
I tried to help with the Polonius optimization effort, by attempting to use arena allocation for some of the data structures that Polonius allocates, but I was unable to find a good speed-up. Then I tried to parallelize Polonius, which took a non-trivial amount of work. After I finally got it to compile, I triumphantly opened a PR… only to find out that Polonius was already parallelized, and all I managed was to implement another nested parallelization, which was usually “parallelizing” work across (almost always) only 1 or 2 items :see_no_evil: This was yet another reminder and a cautionary tale against trying to “micro-optimize” something before I properly understand how it works. Nevertheless, I did learn a bunch of things and found some possible improvements to the parallelization machinery for the future. I also gained some experience with how to apply arena allocation to the Rust compiler, which should come in handy later.
In the end, my experiments at least led to rust#162488, which replaced one data structure in the Polonius implementation, which led to a ~2% instruction count win on a check build of serde. It was a relatively modest win, and just a tiny fraction of the Polonius performance improvements done in the past few weeks, but hey, I’ll take it.
Shipping the parallel frontend
Making the frontend of the Rust compiler parallel is an initiative that has been in progress for many many years. This sentence was also true in 2023, where we hoped to stabilize the parallel frontend within the following few months. Oh well.
Now, it finally looks like we are on the verge of enabling it on nightly by default, thanks to the efforts of Vadim Petrochenkov and several other contributors. I still have only very basic understanding of how the parallelism inside the compiler’s frontend works, and thus wasn’t involved in fixing the myriad of bugs and reproducibility issues with the parallel frontend itself. However, I thought that I might at least help with driving the actual nightly switch forward.
In rust#162848, I’m currently working on doing just that. Mostly it comes down to performing some changes to our testing infrastructure. And I also prepared a blog post to announce this change.
Hopefully, we will be able to land the parallel frontend on nightly in October, to start gathering data from users in the wild.
Measuring compiler performance on ARM
Over the course of 2025, I worked on a Rust Project Goal together with James Barford, where we extended rustc-perf, the Rust compiler benchmarking suite, to support running benchmarks on multiple machines in parallel, which made our benchmarks much faster to execute, and also to support running benchmarks on other architectures than x64. Even though this work got concluded by the end of 2025, we have been actually still running compiler benchmarks only on x64 throughout 2026.
This changed in the last month, where we got access to a cloud ARM machine, and started running rustc-perf benchmarks on it by default. We still don’t yet look at the ARM results that much though, that will require some more tooling and policy changes.
Investigating incremental recompilation
I spent some time to continue my investigations into why incrementally rebuilding bors tests is so damn slow. I found out that the compiler currently doesn’t handle incremental recompilation of async closures very well, and this is a problem for bors, because it is full of them. Essentially, what happens is that everytime you modify code within an async closure, the compiler will recompile a much larger part of your crate than it would necessarily have to. I hacked together a small compiler change that avoids this behavior, which reduced the incremental recompilation time of bors from 14s to 8s (!). However, I don’t know if this is the proper fix, and whether it would have an adverse effect in other benchmarks. I also found out that enabling debuginfo causes more incremental recompilations in bors than when debuginfo is disabled. This is highly suspicious, and I suspect that there is much more to uncover here.
Doing this work will require some sustained focus, and other things came up in the meantime, so I didn’t get to it yet. Maybe I’ll find some time to work on it in the next two months. In the meantime, I tried to nerd-snipe the illustrious nnethercote to take a look at bors, so maybe this won’t be an issue soon :laughing:
Improving the Rust compiler merge queue
Speaking of bors, there were some exciting updates to the merge queue of the Rust compiler in the past few weeks.
In my previous report, I noted that @Mark-Simulacrum started looking into running some of our more expensive and latency-sensitive CI jobs on EC2 runners. This is cool in that it both makes the jobs much faster, but also much cheaper, than running on CodeBuild, which we did previously. I’m happy that we finished this work with Mark in August :tada: which means that our x64 try jobs, which we have to run before starting each compiler performance benchmark, now take only ~1 hour, so they are essentially twice as fast as before. We also started using the EC2 instances for ARM jobs that build compiler artifacts, which is what enabled us to run ARM benchmarks (as noted ) on every merged PR.
We also landed one long-standing feature request and one infrastructure improvement in bors.
The long-standing feature request is the automation of something that we call “r=me after PR CI passes”. When approving PRs in the rust-lang/rust repository, it was quite common for the reviewer to want to approve a PR for which the PR CI hasn’t finished yet. We didn’t have any automation for that, because we use our own merge queue implementation (bors), so we cannot use GitHub’s built-in tooling for this. So the PR author had to wait until PR CI became green, and then approve the PR on behalf of the approver. This was annoying; people sometimes forgot to approve the PR or had to keep a tab open until the CI finished. There was an open issue about this from 2019 in homu, the previous merge queue implementation.
Since we finally switched from homu to bors at the start of this year, it finally became realistic to implement this feature, though it took us a few months before we figured out the right design for it and found the time to implement it. Sakibul Islam, my former GSoC mentee, who is now a member of the bors team, single-handedly implemented and tested the feature (I have to say that it was a joy to review this PR!). Reviewers can thus now simply write @bors r+ and let bors worry about PR CI passing or failing, without having to babysit the PR.
The infrastructure improvement that I mentioned is that rollups are now unrolled by bors. Oh wow, what a sentence! Rollups are PRs that batch multiple other PRs together to amortize the cost of running CI. After they are merged, we “unroll” them by producing compiler artifacts for each separate PR merged with the previous main commit, so that we can run performance benchmarks on each PR separately, to determine which rolled-up PR caused a performance regression (if there are any).
Previously, this unrolling was performed by the rustc-perf bot. This worked relatively well, but it was implemented in a “fire-and-forget” way, so if the unrolling failed, we had no way to retry or even properly learn about the failure, and it required the rustc-perf bot to have permissions for pushing into the rust-lang/rust repository. Moving this feature from rustc-perf to bors was something that I wanted to do for a long time, to make it much more robust, make it possible to implement some cool new features related to triaging the rolled-up performance benchmarks, and also to remove the unnecessary push permissions from the rustc-perf account, thus making our CI safer overall. Merging the rustc-perf PR with almost 450 removed lines was a pretty nice feeling. A lot of this work was initially performed by Sakibul again, I just picked it up and moved it over the finish line.
Josh subtree migration
The rust-lang/rust repository leverages several other git repositories with which it has to be periodically synchronized in both directions. For some of them, we are using git subtree, while for others, we use the awesome Josh tool, about which I previously wrote on the Rust blog.
For several years, we have been trying to migrate the remaining subtrees using git subtree (which is very painful to use) to Josh. Lately, we made a lot of progress on that, and it seems that we are nearing the finish line. The Josh tool received some updates that make it much better in managing our repositories and we implemented some improvements to our josh-sync tool, which wraps Josh to provide rust-lang-specific functionality.
This allowed us to move forward with the five remaining subtrees still managed by git subtree:
rustc_codegen_cranelift: migrated to Josh :tada:.rustfmt: migrated to Josh :tada:.portable-simd: has an open migration PR, which is currently stuck on resolving some CI failures.rustc_codegen_gcc: we attempted to perform a migration, but ran into some issues. We will try again after the next sync.clippy: we resolved several missing features injosh-syncthat should enable the Clippy repository to migrate, though there are still some things left to resolve. Hopefully we will be able to migrate to it soon.
I would (finally!) like to get this over the finish line, so that we can stop using custom patched versions of git subtree to even be able to handle our subtree repositories, and to have all the subtree logic be performed in a unified manner via josh-sync and Josh.
In addition, we are also currently discussing whether it would make sense to move Cargo from a git submodule to a Josh subtree.
Tracking rust-lang crates on crates.io
There are hundreds of “official” or semi-official crates on crates.io that are somehow associated with the Rust Project. Either they are deployed from repositories living under the rust-lang GitHub organization or they are owned by some Rust Project teams. Historically, we did not do a very good job of categorizing and tracking which crates are actually owned by the Rust Project, and which teams should own them though. So many of those crates are currently owned by individual GitHub users, some of which are no longer contributing to Rust anymore.
The Infrastructure team would like to clean that up, first so that we don’t have such a mess in it, but also for improved security. Our goal is to force using Trusted publishing, to ensure that all crate releases happen from CI, so that we at least have some audit trail when releases are made, and make all Rust Project crates owned by the special rust-lang-owner account, which is only accessible to our infrastructure admins, so that it won’t be possible to disable Trusted publishing and perform silent “rogue publishes” of crates, for example if the GitHub account of some Rust Project member would be compromised.
To achieve that, we have to:
- Actually find out which crates should be owned by
rust-lang-owner. This is mostly manual work. Luckily, Eric Huss did a lot of the work already, and put most of the crates that we should own into a big Excel table, so this part is mostly done. - For those crates that are currently not owned by
rust-lang-owner, ask their current owners to invite the special account, so that we can start managing the crate. I started doing this cat herding recently, and it was surprisingly successful. The owner account now owns over 250 crates, vs (I think) less than a hundred that it owned when I started this effort a few weeks ago. People have been very responsive to my crate ownership requests, thank you! - Associate the crates that we manage to specific Rust teams that should (virtually) own them, and repositories from which they should be deployed, in the
teamdatabase. This is an ongoing effort, right now we only track a few tens of those crates. We did not have owners for some crates that we already tracked in theteamdatabase, so I backfilled them in team#2742 and made it mandatory to have at least one team owner for each publishable crate in team#2743. - For the crates that are still expected to have some releases, and are not yet completely deprecated, configure Trusted publishing for them, and configure them to be publishable from GitHub Actions (e.g. using
release-plz). This is the hardest part, because it is not fully automatable, and different teams have different ideas on how they want their crates to be published/released. To make this a bit simpler and avoid doing unnecessary work, I implemented support for tracking crates that are not expected to be published anymore without configuring Trusted publishing for them in team#2775. - Provide a way for Rust teams to yank and unyank crates that they own, without those teams actually being owners of the crate on crates.io (as noted earlier, we want to avoid this to reduce the fallout of compromised accounts). I implemented this in
triagebot(team#2725, triagebot#2497), so now Rust team members can yank and unyank crates on the Rust Zulip server using some chat commands.
Steps 2., 3. and 4. are still ongoing, but they made a lot of progress in the past few weeks. Thanks to that, we were able to go forward with our plan and ensure that rust-lang-owner is the sole owner of all the crates that we track, which I implemented in team#2726.
Now, “all that’s left” to do is to incrementally backfill all the Rust Project crates into the team DB, along with figuring out which team should own them, and configuring Trusted publishing for them if needed.
Reviving unmaintained repositories
In the past two months, I participated in a “revival” of two codebases that were neglected and unmaintained for quite some time, and made substantial improvements to one of them.
thanks.rust-lang.org
The thanks website shows contribution statistics for individual Rust releases, as a way of thanking all the people who contribute to Rust. This tool was created several years ago, and since then it has been mostly chugging along fine. However, we wanted to make some improvements to it, which was quite difficult, because it was implemented in a way where it was counting the contributions for each Rust release starting all the way from the first Rust release. In other words, it was O(n^2), which meant that it took over 10 minutes to generate the contents of the website. That wasn’t really a problem on CI, because it only ran once per day, but it was super annoying for any local experiments and further development.
We wanted to move to a faster algorithm, but it was producing some differences in output. So as a pre-requisite, we first wanted to add tests. Daniel Scherzer implemented end-to-end tests in thanks#99, but it was a bit cumbersome to store snapshots of the generated HTML pages in the repository. So I first implemented a CSV output mode in thanks#105 and thanks#110, which only outputs the contribution statistics into a CSV file, rather than the full HTML page. Then we realized that some of the tests were not deterministic, which turned out to be an issue with missed sorting of git submodules, which I fixed in thanks#111. After that, we could finally merge the snapshot tests. Thanks, Daniel!
After we had some initial tests, I set out to reimplement the contribution counting logic. Originally, I wanted to just modify the original algorithm to avoid the duplicated work, but after some experiments, I decided to reimplement the whole thing from scratch, to make it amenable for parallelization. First, we gather a set of all commits for individual Rust releases and tags, then we remove duplicates and assert that we have counted all commits in the git history. The duplicate check was actually missing in the original implementation, so this rewrite actually fixed a bug, where some commits were previously counted multiple times. After that, we go through all the commits and extract the commit author and reviewer out of it, while taking the mailmap of the rust-lang/rust repository into account. This work was performed multiple times for each commit before, while now it is only performed once per commit. Splitting the work into two explicit stages also made it much easier to parallelize it.
This new algorithm was implemented in thanks#116. The performance results were very nice. Gathering the statistics for all (~100) Rust releases originally took more than 10 minutes, but with my PR it only took ~20 seconds on my laptop. I further made it even faster in thanks#123 by checking out git submodules in parallel. This huge performance improvement actually finally made it possible to experiment with the tool locally, and make improvements to it. It also made CI much faster, which is nice. Sometimes, improving performance is not just about metrics, but it unlocks completely new opportunities.
The main improvement that this allowed us to do was adding support for multiple projects. While the tool already counted contributions from all submodules and subtrees of the main rust-lang/rust repository, it did not take into account other important projects in the Rust toolchain, such as Rustup. In thanks#125, I did some preparatory work to support multiple projects and then in thanks#126 I added support for counting contributions from the rust-lang/rustup repository. Since we now had more than a single project, I also added an overview page in thanks#130, to see all the projects at one place, and a new page that combines all-time contributions from all the tracked projects. Once we added Rustup, I reached out to the crates.io and docs.rs maintainers, and they were fine with also including those projects in thanks, so I did that in thanks#131. And finally, I made the project overview page be the default homepage in thanks#138, so that now that you go to thanks.rust-lang.org, you will see all the tracked projects at one place.
I am quite happy that we finally made the thanks tool not take 10 minutes to execute, I considered that as an affront. We actually wanted to do this for a long time, but the reason why it didn’t happen is that no one felt like they were really owning the tool, so reviews fell through the cracks and nothing moved forward. In July, I finally decided that there is no point in waiting years for someone else to review changes in this repository. I asked a fellow Infrastructure team member if they would be fine if I took over the maintenance of the tool, and they said yes. So I took it upon myself to review and merge changes to this tool, even if it sometimes meant approving a bunch of my PRs myself, without much review. Once I embraced this mindset, I was able to move forward with the improvements at light speed :) And I think that it was overall a good thing for this codebase.
rustup-components-history
The second repository that I picked up after it not being maintained for a long time was rustup-components-history, which powers this page that shows which Rustup components were available for individual Rust targets in the past few days.
It was a similar story as thanks; no one felt like they owned the repository, so no reviews were being made. I found out about this when I was approached by @rami3l, the lead of the Rustup team, if I could help him find a reviewer for his PR that was opened over two years ago. I realized that as a member of the Infrastructure team, that reviewer can simply be me, so I reviewed their PR and finally merged it. Then, by coincidence just a week later, an unrelated change to one Rust target actually broke the components website. I thus went and fixed it, and also gave permissions to the Rustup team to manage this repository. You would think that a tool called rustup-components-history would allow the Rustup team to merge changes to it, but it wasn’t actually configured as such before.
Once I fixed the tool, I spent a bit of effort on doing the usual facelift of Rust repositories:
- Switched the default branch to
main(rustup-components-history#61). - Cleaned up CI (rustup-components-history#62, rustup-components-history#65, rustup-components-history#66).
- Removed unused code and updated the codebase to Edition 2024 (rustup-components-history#63).
This didn’t take much time and it rejuvenated the codebase a bit, which is always nice.
Funding team activities
As a part of the Rust funding team, I helped bootstrap the Maintainer in Residence (MiR) program, by helping to categorize the maintenance needs of various Rust teams and matching them with the funding needs of Rust maintainers. We were able to support 7 people right from the start, which is awesome! You can find out more about them in these two blog posts:
- Announcing our first Maintainers in Residence
- Announcing a Maintainer in Residence: Scott Schafer for the Cargo team
Apart from that, I finished the MiR page on the Rust website (implemented in www.rust-lang.org#2323), and worked on some tooling to help us track the contributions of funded maintainers. I also prepared a charter for the Funding team, to be approved by the Rust Leadership Council.
There is a lot more work to do in the Funding team, but I think that we got off to a good start.
Other things I worked on
- I resumed my refactoring spree of
bootstrap, the Rust compiler build system, this time with a set of PRs that refactored how the build system builds and downloads LLVM from our CI. This continues a long series of refactoring that tries to make bootstrap easier to understand, modify and maintain, and unlocked some further cleanups (rust#161853, rust#161691, rust#160631). - I started working on renaming the
rust-clippyrepository simply toclippyand also renaming its default branch tomain(rust-clippy#17541, rust-clippy#17552). - As usually, I did lots of tiny fixes and improvements to CI, infrastructure, tooling of various
rust-langrepositories, and to our bots. - As usually, I participated in the Rust Compiler performance triage.
- Together with my co-mentors, Jieyou Xu and Folkert de Vries, we concluded the two Rust GSoC 2026 projects that we were mentoring. I think that both of them were quite successful, and we were very excited to work with our mentees, Walnut356 and xonx4l. I will share more details about the projects in my next report, once Rust GSoC 2026 has completely finished.
- I analysed the results of a survey about the performance of the Rust Leadership Council and shared it with members of the Rust Project.
- I wrote a bunch of blog posts on the official Rust blog:
- Together with Lori Lorusso, we prepared another entry of the maintainer spotlight series, this time with @blyxyas.
- Posted an update on the activity of the Leadership Council.
- Announced the Leadership Council elections.
- Announced the nightly experiment to default to
-Zembed-metadata=noin Cargo. - Together with Lori Lorusso, we prepared two announcements (1, 2) about our first Maintainers in Residence :tada:
- I was re-elected as the Infrastructure team’s representative in the Leadership Council. Yay! I hope to continue being useful to the Rust Project in the Leadership Council.
Contribution statistics and pull requests
Below you can find some statistics and a list of PRs that I opened during August and September 2026.
- Opened 262 pull requests in Rust-related repositories.
- With a median of 20 modified lines per pull request.
- Reviewed 214 pull requests in Rust-related repositories.
- Sent 889 comments in the
rust-langGitHub organization. - Sent 2282 public and 1942 private messages on the Rust Zulip.
I want to provide a bit of context regarding the statistics above. After my previous report, where I wrote that I opened 162 PRs in two months, some people reached out to me and essentially asked me whether I am OK :laughing: And that I shouldn’t overwork myself. Given that in this report, that number is (exactly!) 100 PRs higher, I thought that I should explain it a bit.
Those numbers of PRs are not produced by me using LLMs (as I explained previously, I almost never use them for code that gets committed to git) nor by me being a 10x engineer working 24/7. I simply open a large number of usually very small PRs across many projects, given the nature of work that I do in the Rust Project, which often deals with fixing CI, improving infrastructure and tooling, improving docs, etc. I don’t push out 5 big new features every day. That’s why I included the median of modified lines in my PR, to get across the idea that most of my PRs are very small. So take the numbers above with a grain of salt. I mostly put them here for my own historical record.
List of opened PRs
rust-lang/rust (71 PRs)
- #160421: Do not use -Werror when building rustc_llvm with GCC (closed)
- #160427: Run try builds on EC2 by default (merged)
- #160434: Avoid Docker push when the image did not change (merged)
- #160449: Fix lookup of object files (merged)
- #160451: Deduplicate target and host filesearch (merged)
- #160453: Add fast path to
escape_string_symbol(merged) - #160493: Rollup of 29 pull requests (merged)
- #160501: Add bootstrap CLI snapshot test for testing miri (merged)
- #160502: Reduce number of miri tests executed on PR CI (merged)
- #160573: [perf test] Revert “Rollup merge of #159339 - LorrensP-2158466:extract-use-injections, r=petrochenkov” (closed)
- #160574: Update rustc-perf submodule (merged)
- #160579: Do not download GCC from CI in PR CI jobs (closed)
- #160631: Do not eagerly download rustfmt in bootstrap (merged)
- #160645: Assorted bootstrap LLVM refactors (part 1/N) (merged)
- #160681: Respect
--all-targetsflag when checking the compiler (merged) - #160693: Add branch config for perf. unrolling in bors (merged)
- #160839: Stop updating npm lockfile by Renovatebot (merged)
- #160894: Allow running an arbitrary number of try jobs per PR (merged)
- #160916: Assorted bootstrap LLVM refactors (part 2/N) (merged)
- #160970: Fix handling of relative paths starting with a dot in bootstrap (merged)
- #161046: Enable unrolling feature of bors (merged)
- #161102: Explicitly pass run_make_support rlib/rmeta paths to compiletest (merged)
- #161111: Take bors try-perf branch into account in verify-channel.sh (merged)
- #161145: Remove references to the obsolete
try-perfbranch (merged) - #161235: Compute job time in post-merge-report from the actual GitHub duration (merged)
- #161236: Download auto jobs in citool in parallel (merged)
- #161237: Remove jdno from infra-ci rotation (merged)
- #161247: Assorted bootstrap LLVM refactors (part 3/N) (merged)
- #161290: Assorted bootstrap LLVM refactors (part 4/N) (merged)
- #161344: Update the
rustc-perfsubmodule (merged) - #161384: Bust sccache’s cache (merged)
- #161393: Configure LLM policy URL for triagebot (merged)
- #161438: Change triagebot backport to ping T-libs-fcp (merged)
- #161460: Add a
size_hintfunction toDisplay(open) - #161475: Fix checking of LLVM prebuilt status (merged)
- #161483: Warn about running ui-fulldeps tests in stage 1 (merged)
- #161516: Revert #161236 (Download auto jobs in citool in parallel) (merged)
- #161641: Check for missing rustfmt in the stdarch intrinsic test step sooner (merged)
- #161663: Reduce dependency on implicit paths in bootstrap (merged)
- #161666: Print vendor instructions in
x vendor(merged) - #161691: Assorted bootstrap config refactors (part 1/N) (merged)
- #161853: Consolidate LLVM skip in check builds in bootstrap (merged)
- #161992: [do not merge] ARM perf experiments (closed)
- #161995: [perf] Use SmallVec in
smart_resolve_path(closed) - #162004: Remove unneeded clone in macro deriving (merged)
- #162006: Use a simple arena for annotatable items (closed)
- #162053: Add bootstrap command to check all Tier 2+ targets (open)
- #162092: Implement test- naming convention for CI jobs (merged)
- #162111: Update mailmap for Will Crichton and Petr Hosek (merged)
- #162328: Allow overriding filecheck even if LLVM is built or downloaded (merged)
- #162371: Cache sanitizer set in
Session(merged) - #162413: Unconditionally invalidate the library when the compiler changes (merged)
- #162428: Pin the number of frontend threads while gathering PGO to 1 (merged)
- #162473: Small
x perfimprovements (merged) - #162480: Mono item collection microoptimizations (closed)
- #162485: Parallelize gathering constraints in the borrow checker (closed)
- #162488: Use
DenseBitfordrop_live_atin liveness tracing (merged) - #162512: Implement
ExactSizeIteratorforChain(closed) - #162524: Use
SmallVecinTokenCursor(closed) - #162533: Use
share-genericsfor libstd (closed) - #162593: [perf] Try to optimize
collect_pos(open) - #162777: Remove allocation from
mbe::quoted::parse(closed) - #162780: Use
-Clinker-plugin-ltoin bootstrap (closed) - #162848: Use 2 parallel frontend threads by default on the nightly and dev channel (open)
- #163212: rustfmt subtree update (closed)
- #163412: Apply LTO to Cranelift and GCC codegen backends (open)
- #163433: Support also
try-jobs:to specify custom try jobs (merged) - #163436: Stabilize
-Zembed-metadata(open) - #163444: Add
stable_rustchelper inrun-make-support(merged) - #163452: Use newtype enums for representing frontend and backend jobs (open)
- #163533: Run cg_gcc tests with the correct compiler (open)
rust-lang/team (36 PRs)
- #2648: Move inactive t-triage members to alumni (merged)
- #2649: Add Zulip topic for the funding team and MiRs (merged)
- #2662: Configure branches for unrolled perf builds for bors (merged)
- #2669: Add first batch of Maintainers in Residence (merged)
- #2670: Add Tyler Mandry to funding advisors (merged)
- #2674: Change default branch of
rust-clippytomain(open) - #2679: Add ruleset for the
thanksrepo (merged) - #2680: Remove
rust-timerbranches fromrust-lang/rust(merged) - #2681: Remove obsolete merge bot variants (merged)
- #2682: Archive the
rustc-rayonrepository (merged) - #2696: Rename the default branch of
triagebottomain(merged) - #2697: Change default branch of the
forgerepository tomain(merged) - #2700: Add check for duplicated alumnis and remove them (merged)
- #2713: Add lcnr and Joel Marcey to funding advisors (merged)
- #2725: Add crates to the v1 API (merged)
- #2726: Ensure that only
rust-lang-ownerowns our tracked crates (merged) - #2727: Track Cargo crates (open)
- #2735: Point the
funding@rust-lang.orge-mail list to the funding team (merged) - #2742: Assign owner teams to crates.io crates (merged)
- #2743: Force team ownership of crates (merged)
- #2744: Set compiler team as co-owners of
annotate-snippets(merged) - #2751: Add branch protection for the
goalsrepo (merged) - #2758: Give the Rustup team access to
rustup-components-history(merged) - #2759: Change the default branch of portable-simd to main (merged)
- #2767: Add Scott Schafer to the Maintainers in Residence team (merged)
- #2772: Give security response merge rights to the blog (closed)
- #2775: Allow managing unpublishable crates (merged)
- #2777: Process crates without trusted publishing (merged)
- #2778: Manage
rustup-available-packagescrate (open) - #2786: Add homepage to the funding team repo (merged)
- #2787: Archive the
pin-utilsrepository (open) - #2788: Update Council libs rep (merged)
- #2789: Manage Council private Zulip stream (merged)
- #2792: Sort bypass actors (merged)
- #2793: Create
trusted-contributorsteam (merged) - #2795: Add Predrag to trusted-contributors (open)
rust-lang/bors (34 PRs)
- #799: Tag EC2 instances launched by bors (merged)
- #800: Add web page with EC2 instance list (merged)
- #801: Add links to the queue page (merged)
- #802: Implement backfilling of EC2 instances (merged)
- #803: Improve layout of pending builds table (merged)
- #804: Strip auto/try job prefixes (merged)
- #806: Try to start EC2 instances on any branch (merged)
- #807: Reload in-memory job cache when bors starts (merged)
- #808: Document Zulip posting and EC2 instance spawning (merged)
- #809: Only consider workflow run webhooks with the
pushevent (merged) - #812: Allow opting out of the maximum try job limit (merged)
- #813: Add hint about
@bors try nolimit(merged) - #815: Add a hint about retrying PR CI when someone uses
@bors retryin an invalid state (merged) - #816: Store
pr_numberfield in thebuildtable (merged) - #817: Add rollup unrolling (merged)
- #818: Add a hint to
@bors try cancel(merged) - #819: Trigger the merge queue when the priority of a PR changes (merged)
- #820: Correctly parse unrolled member build kind from EC2 instance tags (merged)
- #821: Fix termination of multiple EC2 instances (merged)
- #823: Close PRs in DB that disappear from GitHub (merged)
- #827: Ignore homu-ignore blocks in squashed commit messages (merged)
- #833: Add a sanity check for valid bors config before merging a PR (merged)
- #834: Fix decoding base64 GitHub contents (merged)
- #837: Check that EC2 instance is in
allowed_instances(merged) - #838: Add in-memory cache of spawned EC2 instances (merged)
- #839: Check config validity post merge (merged)
- #850: Do not link to GitHub PRs when doing rollup mergeability check (merged)
- #854: Add a test for not merging a tentatively approved PR (merged)
- #858: Upgrade tentative approvals if the PR was already fully approved (merged)
- #859: Make tentative approvals less obtrusive (merged)
- #862: Apply approval labels eagerly when a PR is tentatively approved (merged)
- #863: Support also the
try-jobscustom try job marker (merged) - #864: Set
approval_tentativetoFALSEinunapprove_pull_request_if_sha_changed(merged) - #865: Hotfix database state (closed)
rust-lang/rustc-perf (33 PRs)
- #2520: Add support for bare rustc invocation with
--print-sysroot(merged) - #2521: Temporarily fix the
serde4 threads benchmark (merged) - #2522: Create three NLL benchmarks (merged)
- #2524: Remove unrolling functionality (merged)
- #2526: Remove accidentally committed files (closed)
- #2533: Fix handling of try builds that were not enqueued (merged)
- #2534: Ignore comments from bors (merged)
- #2535: Read bors try build completed comments (merged)
- #2538: Add 2026-08-18 triage (merged)
- #2539: Add crate metadata to compare page quick links (merged)
- #2541: Include profile in error message about unavailable artifacts (merged)
- #2550: Improve support for ARM benchmarks (merged)
- #2551: Correctly store targets of benchmark requests into the database (merged)
- #2555: Respect optional job attribute when checking parent jobs (merged)
- #2556: Separate artifact size records by target (merged)
- #2560: Ignore runtime benchmarks when a benchmark filter is set (merged)
- #2564: Fix duration estimation (merged)
- #2565: Speed up triage command (merged)
- #2566: Run ARM benchmarks by default (merged)
- #2567: Fetch GitHub commits concurrently in the triage command (merged)
- #2568: Add support for frontend threads to the UI (merged)
- #2571: Add
tokio-1.53.1benchmark (merged) - #2572: Share target directory for different values of frontend threads (merged)
- #2573: UI improvements related to frontend threads (merged)
- #2574: Allow benchmark to opt into different values of frontend threads (merged)
- #2575: Remove the
serde-1.0.219-threads4benchmark (merged) - #2579: Fix normalization of profiles and scenario in the frontend (merged)
- #2580: Add 2026-09-14 triage (merged)
- #2581: Run
apt updatebefore installing Valgrind (merged) - #2585: Allow filtering Clippy benchmarks in the compare page (merged)
- #2586: Use
--jobs-frontendinstead of-Zthreads(open) - #2594: Make
parse_benchmarksinfallible (merged) - #2596: Improve support for different codegen backends (merged)
rust-lang/thanks (26 PRs)
- #109: Run Clippy and rustfmt on CI (merged)
- #110: Write all-time data in CSV output mode (merged)
- #111: Sort submodules to fix non-deterministic commit iteration (merged)
- #112: Add conclusion CI job (merged)
- #113: Force handling of submodules (merged)
- #114: Sort also by e-mail author while deduplicating (merged)
- #115: Update to edition 2024 (merged)
- #116: Change processing of commits to walk all commits only once (merged)
- #117: [perf] checkout submodules in parallel (closed)
- #118: Set
line-tables-onlydebuginfo in release mode (merged) - #119: Refresh repositories using an environment variable, not using a CLI flag (merged)
- #120: Use cache in CI (merged)
- #121: Add all-time snapshot (merged)
- #122: Make tests self-contained (merged)
- #123: Checkout submodules in parallel (merged)
- #124: Allow overriding the mailmap (merged)
- #125: Prepare for multiple projects (merged)
- #126: Add support for Rustup (merged)
- #130: Add page with all tracked projects (merged)
- #131: Track crates.io and docs.rs (merged)
- #132: Ignore bots in docs.rs (merged)
- #133: Ensure that deploys are never executed concurrently (merged)
- #138: Make the projects page be the homepage (merged)
- #141: Make the regression test optional (merged)
- #143: Use deduplicated scores for people and commit count (merged)
- #144: Ignore dependabot and renovatebot by default (merged)
rust-lang/blog.rust-lang.org (10 PRs)
- #1910: Add post about using
-Zembed-metadata=noby default on nightly (merged) - #1911: Add Leadership Council September 2026 announcement post (merged)
- #1913: Clarify that
-Zembed-metadata=nois an experiment (merged) - #1914: Add
Announcing our first Maintainers in Residenceblog post (merged) - #1927: Build Zola in debug mode (merged)
- #1931: Fix placeholder date validation for Inside Rust posts (merged)
- #1942: Add Enabling the parallel frontend on nightly post (open)
- #1943: Add Leadership Council September 2026 update post (merged)
- #1947: Add Maintainer spotlight interview with blyxyas (merged)
- #1948: Add
Announcing a Maintainer in Residence: Scott Schafer for the Cargo teampost (merged)
rust-lang/triagebot (8 PRs)
- #2484: Run CI on
mainbranch (merged) - #2485: Deny warnings on CI (merged)
- #2486: Add LLM policy URL config (merged)
- #2487: Fix sending of Zulip DMs (merged)
- #2492: Fix sending of Zulip DMs (take 2) (merged)
- #2497: Implement crate yanking/unyanking on Zulip (merged)
- #2498: Fix channel name in yank check (merged)
- #2510: Stop trusting Zulip custom profile fields in the
lookupcommand (merged)
rust-lang/josh-sync (7 PRs)
- #56: Update josh-sync version (merged)
- #57: Fix CI check (merged)
- #59: Load nightly commit SHA from CI instead of from
rustc(merged) - #60: Include nightly version in merge commit message (merged)
- #62: Allow specifying nightly date in
pull --upstream-commit(merged) - #63: Allow pushing commits with the SSH protocol (open)
- #64: Allow continuing with a pull when a merge conflict happens (merged)
rust-lang/rustup-components-history (7 PRs)
- #60: Parse tier tables by their ID, not position (merged)
- #61: Switch default branch to main (merged)
- #62: Update CI (merged)
- #63: Remove unused code (merged)
- #64: Reduce rightward drift (merged)
- #65: Deploy GitHub Pages from the workflow (merged)
- #66: Build the repo on CI in release mode (merged)
rust-lang/rust-clippy (4 PRs)
- #17541: Build docs into the
maindirectory (merged) - #17543: First add files to git before checking diff in the deploy script (closed)
- #17552: Modify doc links to point to
maininstead ofmaster(merged) - #17556: Rename default branch from
mastertomain(open)
rust-lang/rust-forge (4 PRs)
- #1098: Rename the default branch to
main(merged) - #1099: Document
llm_policy_url(merged) - #1105: Switch governance owner from ehuss to LC (merged)
- #1106: Document crate yanking in triagebot (merged)
rust-lang/cargo (2 PRs)
- #17413: Micro-optimize two package dir functions (merged)
- #17426: Use trusted publishing for Cargo crates (open)
rust-lang/portable-simd (2 PRs)
rust-lang/rustc-dev-guide (2 PRs)
- #3027: Note that cg-clif is using Josh now (merged)
- #3032: Mark
rustfmtas being managed by Josh (merged)
rust-lang/rustc_codegen_cranelift (2 PRs)
rust-lang/rustfmt (2 PRs)
rust-lang/simpleinfra (2 PRs)
- #1204: Configure rustc-perf Ansible for Aarch64 (merged)
- #1222: Increase CPU allocation for triagebot to one full CPU (merged)
rust-lang/stdarch (2 PRs)
- #2225: Refactor generation to use a unified formatting approach (merged)
- #2231: Pull from rust-lang/rust (merged)
rust-lang/www.rust-lang.org (2 PRs)
- #2330: Fix button overflow on funding page on mobile (merged)
- #2335: Add Scott Schafer to the MiR page (merged)
rust-lang/crater (1 PR)
- #851: Add MIT/Apache2 license files (open)
rust-lang/funding (1 PR)
- #10: Add Funding team charter (open)
rust-lang/funding-private (1 PR)
- #1: Add MiR check-in script and contribution analysis script (merged)
rust-lang/goals (1 PR)
- #755: Fix website link in README (merged)
rust-lang/rustc_codegen_gcc (1 PR)
- #983: Rename rust-toolchain to rust-toolchain.toml (merged)
tokio-rs/tracing (1 PR)
- #3603: Use a function to convert from
Leveltolog::Levelto reduce the amount of generated code (open)
Conclusion
These two months felt pretty nice again. I keep worrying about the direction the IT world, and our society, is heading in with the advent of LLMs, and this sometimes makes me feel under the weather, but being funded for open source Rust work is usually enough to get my mood up. I also met a bunch of Rust enthusiasts at a Rust meetup in Prague where I had a talk about how Rust makes parallelism safe(r), and I started teaching Rust again, because the university semester has just started, so that is nice.
As always, I’d like to thank other members of the Rust Project and other Rust contributors, who collaborated with me, discussed various things with me, and reviewed my code in this time period. Thank you very much!
I would also like to thank the Sovereign Tech Agency for funding my open source maintenance work! I am really grateful for this opportunity.
As noted in my previous report, I am currently enrolled in GitHub Sponsors. I did not advertise it much, because I currently have funding for my open source work through the Sovereign Tech Fellowship. However, that is for one year (and it also isn’t full-time), and it is hard to say whether I will be able to find the next source of funding after that. So it is good to have at least some backup. If you’d like to support my open source Rust work, I would really appreciate it! You can find more ways of supporting Rust Project maintainers here.
If you have any questions regarding my upstream Rust work, feel free to ask on Reddit.
- Note that it doesn’t work to keep this change local only to the lexer, because it produces the same tokens that the parser and the AST then works with. Using the flat representation in the lexer, and another representation in the parser, requires converting between them, which makes compilation slower overall, even if lexing is sped up (I tried).
- Yes, there is a second representation of token streams in the compiler that also uses the separately allocated
Vecs, which is used for attributes. It is quite similar, but not quite the same, asTokenStream. Don’t ask me why. - I previously wrote about this suite here.
- If you’re wondering how it makes sense to run CI on each rolled-up PR when we wanted to avoid that cost in the first place, the difference is that the full CI runs ~90 jobs, while for the perf. test we only need a single job for each PR.
- Yes, I am aware that the sibling bors PR actually added almost 3 thousand lines :laughing: But a lot of that were generated
.sqlxmetadata changes, and also now we actually have proper tests for this rather complex feature. - There was one person who initially didn’t trust my request to invite the owner account to own their crate. Fair enough, you shouldn’t trust random internet strangers! Luckily I was able to prove on Zulip that I am who I claimed to be :)
- I used a median, rather than an average, because I opened one PR recently that had 1.6 million modified lines :laughing: And only after it was merged I realized that most of that was some accidentally committed leftover profiling data. We force-pushed the changes away, but this PR skewed the average to over 8000 lines :laughing: That’s why it’s hard to take similar statistics at face value :)