26 Aug 2026

feedPlanet Mozilla

Serge Guelton: Pros and Cons of Unified Build

Unified builds (also know as Jumbo Builds) is a build techniques that aims at improving build time through the concatenation of several sources as a single unified source before compilation.

The goal is obtained through implicit caching of header instantiation, although it implies a trade-off with parallelism.

Let's illustrate this behavior through a simple example, two codes that implement variation of the same approach:

/* algo0.cpp */
#include <iostream>
#include <string>
#include <vector>
void translate(std::vector<std::string>& w, void (&t)(std::string&));
void translate(std::vector<std::string>& w_out, std::vector<std::string> const & w_in, void (&t)(std::string&)) {
    std::cout << "[log] through transform\n";
    w_out = w_in;
    translate(w_out, t);
}

/* algo1.cpp */
#include <algorithm>
#include <iostream>
#include <string>
#include <vector>
void translate(std::vector<std::string>& w, void (&t)(std::string&)) {
    std::cout << "[log] through for_each\n";
    std::for_each(w.begin(), w.end(), [&t](std::string& s) { t(s); });
}

Compiling individual files take the following times:

% hyperfine --warmup 5 "/usr/bin/clang++ -O2 algo0.cpp -c"
Benchmark 1: /usr/bin/clang++ -O2 algo0.cpp -c
  Time (mean ± σ):     267.7 ms ±   7.4 ms    [User: 234.6 ms, System: 30.4 ms]
  Range (min  max):   259.7 ms  277.3 ms    11 runs

% hyperfine --warmup 5 "/usr/bin/clang++ -O2 algo1.cpp -c"
Benchmark 1: /usr/bin/clang++ -O2 algo1.cpp -c
  Time (mean ± σ):     173.6 ms ±  46.3 ms    [User: 149.6 ms, System: 22.1 ms]
  Range (min  max):   130.4 ms  231.6 ms    13 runs

Creation of the unified file is just a matter of invoking cat, let's benchmark the compilation of the unified source:

% cat algo{0,1}.cpp > unified_algo.cpp
% hyperfine --warmup 5 "/usr/bin/clang++ -O2 unified_algo.cpp -c"
Benchmark 1: /usr/bin/clang++ -O2 unified_algo.cpp -c
  Time (mean ± σ):     223.8 ms ±  64.5 ms    [User: 193.0 ms, System: 28.4 ms]
  Range (min  max):   160.8 ms  301.8 ms    10 runs

In that simple case, the weight of headers with respect to actual user code is such that compilation of the unified file takes almost the same time as the max compilation time among each individual file. That's roughly a 1.97x speedup on compilation time.

That's the promise given by unified builds. And it's a promise held.

Now let's have a look at the consequences of that deal.

Beforehand, we still need to introduce another parameter tied to unified builds: the unification parameter, say P. That parameter bounds the number of files that are unified together. Let's imagine we have a hundred of individual source files compiled with exactly the same compilation flags. Setting P to 5 leads to the generation of 20 unified sources compiled independently.

Remember the parameter P.

Quality of the Generated Code

Let's create a shared object from algo{0,1}.o (this implies a recompilation with -fPIC of the sources):

% /usr/bin/clang++ -O2 algo0.cpp -c -fPIC
% /usr/bin/clang++ -O2 algo1.cpp -c -fPIC
% /usr/bin/clang++ -shared algo{0,1}.o -fPIC -o algo.so

And do the same from unified_algo.o:

% /usr/bin/clang++ -O2 unified_algo.cpp -c -fPIC
% /usr/bin/clang++ -shared unified_algo.o -fPIC -o unified_algo.so

After stripping, comparing the size of the binaries yield a difference of a few bytes. After disassembling, it turns out the compiler decides to inline the call to void translate(std::vector<std::string>& w, void (&t)(std::string&)) from algo0.cpp when compiling the unified source, something the compiler cannot do when doing split compilation, as it does not know anything about the implementation of that function.

Interestingly, compiling with -flto=thin still lead the compiler instantiation through different optimization path.

Falling back to -flto=full finally yields to the same shared object, which makes sense because Full LTO is very close to performing source unification at the bytecode level and our sources are very simple. It's not a given though because the actual optimisation pipeline is still different in the two scenario.

Why does it matter? Depending on the value of P, the compiler will see different sets of files per unified file, which will result in different binary code. It's actually even worse: depending on the way we fill those unification sets, event with the same parameter P, we end up with different binaries. Let's call that the reunifying problem.

Even if we have an algorithm that seems to guarantee reproducibility, for instance working on a sorted list of files with a fixed P, variation can arise: the introduction of a new source file can lead to changes in every unified file (e.g. if the split is done by chunks and the new file ends up at the beginning of the file list).

So unified builds tend to improve performance, but they do not interact in a gentle way with performance reproducibility.

Recompilation Times

Let's denote S as the number of sources and C as the number of CPUs.

Intuitively, setting P=1 yields to the faster recompilation time when a single file is touched---a usual scenario when developing a new feature.

On the opposite, setting P=S yields to the slower recompilation time (if S >> C!) under the same scenario as all sources are recompiled under that scenario.

The form of the curve between those two extreme varies depending on the nature of the files, and the amount of header sharing between individual sources.

Caching tools like sccache is impacted by the same mechanism: as P gets greater, more cache misses are hit and more recompilation are done.

Marginally, introducing a new source also pollutes the cache or triggers recompilation for the unified source it gets added to, and eventually for all the unified sources derived from the associated file list. The reunifying problem strikes again.

So unified build make compilation faster, but recompilation slower. Setting P to an acceptable value is important depending on the usage scenario.

Correctness

Unified build changing the compilation unit frontier, which in turns modifies the semantic of the program. This change can be straight-forward or complex to debug, and even remain silent. I've listed a few instances of the two first categories below, and a crafted one for the latter category.

Macro / Symbol Redefinition

This one is trivial to spot (a preprocessor-warning is issued for the macro, and a compiler error is issued for the symbol redefinition):

/* pi0.cpp */
#define PI 3.141593
constexpr double pi() { return 3.141593; }

/* pi1.cpp */
#define PI 3.14159265
constexpr double pi() { return 3.14159265; }

The solution usually lies in moving the definition in a shared header, moving the declaration in a shared header and the definition in a single file, or renaming identifiers to avoid the name conflict. Note that depending on the solution we may change the visibility of the symbols, or impact code readability (assuming the identifier name was perfectly chosen in the first place).

Overload Conflicts

This one is also trivial to spot and may hint toward debatable design. But it exists and may be more complex to understand than the above:

/* overload0.cpp */
static float doit(float f) { return f;}
const float f = doit(1);

/* overload1.cpp */
static double doit(double d) { return d;}
const double d = doit(1);

The fix is generally to provide a perfect match for the overload, change the call site to avoid the ambiguity, or rename the functions/change their namespace to make the call site explicit.

Using Namespace Confusion

This one tends to creep a lot in codebase where using namespace is used. It generates ambiguity among potential symbols.

A caricatured situation is exhibited with the following situation:

/* using.h */
#pragma once
namespace a {
    namespace a {}
}

/* using0.cpp */
#include "using.h"
using namespace a;

/* using1.cpp */
#include "using.h"
using namespace a;

Once using{0,1}.cpp unified, the second using namespace a; directive is ambiguous.

A more realistic (but similar in spirit) situation arises when the same symbol is defined in different namespaces:

/* namespace0.cpp */
namespace a0 {
    int var;
}
using namespace a0;
int foo = var;

/* namespace1.cpp */
namespace a1 {
    int var;
}
using namespace a1;
int bar = var;

The problem with that category is that the fix is quite unsatisfying: there is no way to limit the scope of a using directive, removing using directive can lead to very verbose codebase, renaming symbols to avoid conflicts goes against the very purpose of namespaces...

Delicatessen

I spent a lot of time nailing that one down, so I wrote a small reproducer to illustrate the problem.

% tail -n +1 *.h *.cpp
==> header0.h <==
#ifndef H0
#define H0
namespace mozilla::dom {

class Lock final {};

}
#endif

==> header1.h <==
#ifndef H1
#define H1

#include "header0.h"

class Lock {};

class AutoUnlock {
    Lock *lock_;
};
#endif

==> src0.cpp <==
#include "header1.h"

==> src1.cpp <==
#include "header0.h"

==> src2.cpp <==
namespace mozilla::dom {};
using namespace mozilla::dom;
using namespace mozilla;


==> src3.cpp <==
#include "header1.h"

Let me comment that layout a bit: We basically have two different classes named Lock: one lives in the mozilla::dom namespace, and one lives at top-level. In header1.h, although we include the definition of mozilla::dom::Lock, we also get the definition of ::Lock, so a straight reference to Lock is not ambiguous.

Concerning source files, src0.cpp, src1.cpp and src3.cpp just include headers while src2.cpp contains the infamous using namespace modilla::dom; statement.

Let's now consider various partition of the file list src0.cpp, src1.cpp, src2.cpp, src3.cpp:

% for perm in 0,1 2,3 0,1,2 1,2,3 0,1,2,3; do printf "unifying $perm... " ; cat `eval echo src{$perm}.cpp` | clang++ -xc++ - -fsyntax-only 2>/dev/null && echo ok || echo ko ; done
unifying 0,1... ok
unifying 2,3... ko
unifying 0,1,2... ok
unifying 1,2,3... ko
unifying 0,1,2,3... ok

Isn't that amazing? Some intermediate unification, namely 0,1;2,3 and 0;1,2,3 fail, but other unifications, namely 0,1,2,3 and 0;1,2,3 fail. Did you notice that both non-unified and full unified build succeeds, while some intermediate unification fail? What a disaster. This basically mean that given a set of sources, and without putting restriction on the language (like banning using statement), the only way to be sure that a unified build always succeeds whatever the chosen partition is to test every partition. Not very satisfying.

As a side effect, we can also deduce that adding a new source file to a set of files to be unified can break compilation in files that used to compile fine. That's another instance of the reunifying problem.

Changing Semantic

It is quite easy to derive from the above an example whose semantic change once unified. Let's slightly change the overload conflict example from above:

/* silent0.cpp */
#include <cstdio>
static int doit(int f) { putchar('0'); return f;}
const int f = doit(1);

/* silent1.cpp */
#include <cstdio>
static double doit(double d) { putchar('1'); return d;}
const double d = doit(1);

When compiled independently, this results in a binary that prints a 0 and a 1 on the screen. But when compiled as a unified source, we only get a pair of 0.

Concluding Words

Remember that discussion between Luke and Yoda?

LUKE Vader. Is the dark side stronger?

YODA No… no… no. Quicker, easier, more seductive.

That's exactly my thoughts on unified builds: they give you quick wins in term of cold build speed and give faster builds. That's very good properties, and you rip the benefit of them very quickly. Then you realize that you're tied to a monster in terms of maintainability and developer experience, but you're already addict to the speed it gave you.

26 Aug 2026 10:00pm GMT

This Week In Rust: This Week in Rust 666

Hello and welcome to another issue of This Week in Rust! Rust is a programming language empowering everyone to build reliable and efficient software. This is a weekly summary of its progress and community. Want something mentioned? Tag us at @thisweekinrust.bsky.social on Bluesky or @ThisWeekinRust on mastodon.social, or send us a pull request. Want to get involved? We love contributions.

This Week in Rust is openly developed on GitHub and archives can be viewed at this-week-in-rust.org. If you find any errors in this week's issue, please submit a PR.

Want TWIR in your inbox? Subscribe here.

Updates from Rust Community

Official
Project/Tooling Updates
Observations/Thoughts
Rust Walkthroughs
Miscellaneous

Crate of the Week

This week's crate is swift-topomap, a microarchitectural observability tool.

Thanks to Ankur Rathore for the self-suggestion!

Please submit your suggestions and votes for next week!

Calls for Testing

An important step for RFC implementation is for people to experiment with the implementation and give feedback, especially before stabilization.

If you are a feature implementer and would like your RFC to appear in this list, add a call-for-testing label to your RFC along with a comment providing testing instructions and/or guidance on which aspect(s) of the feature need testing.

Rust

Cargo

No calls for testing were issued this week by Rustup or Rust language RFCs.

Let us know if you would like your feature to be tracked as a part of this list.

Call for Participation; projects and speakers

CFP - Projects

Always wanted to contribute to open-source projects but did not know where to start? Every week we highlight some tasks from the Rust community for you to pick and get started!

Some of these tasks may also have mentors available, visit the task page for more information.

If you are a Rust project owner and are looking for contributors, please submit tasks here or through a PR to TWiR or by reaching out on Bluesky or Mastodon!

CFP - Events

Are you a new or experienced speaker looking for a place to share something cool? This section highlights events that are being planned and are accepting submissions to join their event as a speaker.

If you are an event organizer hoping to expand the reach of your event, please submit a link to the website through a PR to TWiR or by reaching out on Bluesky or Mastodon!

Updates from the Rust Project

593 pull requests were merged in the last week

Compiler
Library
Cargo
Rustdoc
Rustfmt
Clippy
Rust-Analyzer
Rust Compiler Performance Triage

A busy week, with a continued stream of improvements to the next trait solver and next borrow check implementations. Other than those changes, the week was pretty quiet for performance.

Triage done by @simulacrum. Revision range: 8fa1c96c..9a4ad59a

2 Regressions, 4 Improvements, 2 Mixed; 2 of them in rollups. 28 artifact comparisons made in total.

Full report here

Approved RFCs

Changes to Rust follow the Rust RFC (request for comments) process. These are the RFCs that were approved for implementation this week:

Final Comment Period

Every week, the team announces the 'final comment period' for RFCs and key PRs which are reaching a decision. Express your opinions now.

Tracking Issues & PRs

Rust

Rust RFCs

Cargo

Compiler Team (MCPs only)

Leadership Council

No Items entered Final Comment Period this week for Language Team, Language Reference or Unsafe Code Guidelines. Let us know if you would like your PRs, Tracking Issues or RFCs to be tracked as a part of this list.

New and Updated RFCs

Upcoming Events

Rusty Events between 2026-08-26 - 2026-09-23 🦀

Virtual
Africa
Asia
Europe
North America
Oceania

If you are running a Rust event please add it to the calendar to get it mentioned here. Please remember to add a link to the event too. Email the Rust Community Team for access.

Jobs

Please see the latest Who's Hiring thread on r/rust

Quote of the Week

I care about this community, including its human and social nature. I want others to appreciate those qualities, and I don't want to see them compromised and replaced by excessive machine-generated content.

- Quine Dot on rust-users

Thanks to Jonas Fassbender for the suggestion!

Please submit quotes and vote for next week!

This Week in Rust is edited by:

Email list hosting is sponsored by The Rust Foundation

Discuss on r/rust

26 Aug 2026 4:00am GMT

The Rust Programming Language Blog: Announcing our first Maintainers in Residence

We are very happy to announce the Rust Project's first round of Maintainers in Residence: Gen Li (@rami3l), Chris Denton (@ChrisDenton), Alejandra González (@blyxyas), León Liehr (@fmease), and Maintainer Grant recipients: Jason Newcomb (@Jarcho) and Jonas Böttiger (@joboet). These contributors will be funded for their rust-lang maintenance activities for (at least) the following 12 months!

The funding of the Maintainer in Residence (MiR) and Maintainer Grantee roles is possible thanks to generous donations to the Rust Foundation Maintainers Fund (RFMF) from Google, AWS, OpenAI, the Rust Project Leadership Council and also individual sponsors. We also want to thank the people who advocated for maintainer funding within their companies; Tyler Mandry from Google, Niko Matsakis and Jess Izen from AWS and Predrag Gruevski from OpenAI, and also the whole Rust Leadership Council and our funding advisors. If you would like to help us support even more Rust contributors, consider donating to RFMF.

The Rust Foundation has published a press release and a blog post, where you can learn more about the sponsors and the supported contributors.

Read more below to learn about the MiR program, how we chose the funded contributors, and of course who they are!

Background

The Maintainer in Residence program, established in RFC 3931, is designed to provide stable financial support for Rust contributors, so that they can truly focus on crucial maintenance activities. Currently, there are three categories of support that we offer:

Funding for this program comes from the Rust Foundation Maintainers Fund, which was launched recently, and the whole program is managed by the Rust Funding team.

When deciding who to fund, we took a systematic approach. First, we looked at Rust teams to understand their maintenance baseline (the smallest number of maintainers they need to ensure a healthy long-term status of the given project or repository), and how far they currently are from that baseline. From there, we identified and prioritized Rust teams who were both critically underfunded, and have a high impact on the language and its users. These teams (in no particular order) were rustdoc, rustup, cargo, compiler, libs, clippy, rustfmt, rust analyzer and mods.

The next step was pairing these teams with maintainers looking for funding. And it turns out that finding such maintainers for some teams turned out to be much more difficult than we originally assumed! For example, some maintainers are already employed, some do not want to be funded, and while we did our best to promote our funding efforts, not everyone looking for funding actually asked us for it. We also realized that some teams on our list have essentially no active members, which makes it tricky to onboard new contributors, even if they would like to help out.

In the end, we decided to start by supporting six contributors, who will help maintain several critical Rust projects and teams and who could start immediately. However, we are not stopping there. Our funding efforts are ongoing, so stay tuned for more MiR announcements in the near future! If you would like to learn more about our process, check out our recent post.

And now, without further ado, let's meet our newly funded maintainers!

Gen Li (@rami3l)

Gen Li (@rami3l) is a full-time MiR focusing on Rustup.
He has been a Rustup team member since 2023 and its lead since 2025. He deeply cares about the facets of Rust that many might have taken for granted, and embodies all attributes we were looking for in a MiR: he wants to take on complex issues, continue mentoring, and work on important Rustup features, among many other things.

Turning volunteering into an actual job has really been an empowering experience so far! I finally have the bandwidth to take a careful look at my inbox and can actually read each message without the fear of missing crucial details while rushing prompt replies, which has really helped me retain the essential compassion as a maintainer. I also get to interact with regular contributors a lot more often. Finally, I can't wait to see what I can come up with in terms of Project Goals :)

Chris Denton (@ChrisDenton)

Chris Denton (@ChrisDenton) is a half-time MiR focusing on the standard library, compiler, Rustup and anything Windows-related.
For the past five years Chris has been bringing his deep knowledge of Windows to help Rust sustain and improve its great cross-platform support. He will be unblocking other contributors in various Windows use cases, performing refactoring and code reviews and implementing new features across several areas of the Project.

Even though it is still early days, I'm feeling pretty optimistic about the health of the Rust Project going forward, thanks to the recent funding efforts.

Alejandra González (@blyxyas)

Alejandra González (@blyxyas) is a half-time MiR focusing on Clippy.
She is a Clippy team member always keen on improving performance and helping new contributors. She will focus on making Clippy faster and also reviewing its pull requests, to help get the ~300 pull request backlog down. Additionally, she is excited to mentor people from the Rust for Linux project to work on Clippy, and fine tune the open peer review system that Clippy started using earlier this year.

Funding is the system that helps me pour my heart into a project without worrying about making ends meet. Having those needs met is a game-changer and boosts my productivity. One of the areas where I want to focus my efforts is mentoring new contributors. If new people coming is the lifeblood of a project, I want to be the cardiologist!

León Liehr (@fmease)

León Liehr (@fmease) is a half-time MiR focusing on rustdoc and the compiler.
He is a member of the rustdoc and compiler teams, who is usually working on the Rust type system or issues related to parsing. He will continue working on complex features that he started a few years ago, and also focus on general maintenance, code reviews, refactoring and mentoring.

Being funded to work on Rust means I can sustainably focus my time and energy on a project I call a passion of mine.

Jonas Böttiger (@joboet)

Jonas Böttiger (@joboet) is a maintainer grantee focusing on the standard library.
He is a musicology student from Germany. When he is not playing the Cello or reading about Fanny Hensel, he applies his research skills to ensure that programs written in Rust run quickly and soundly on all platforms, no matter how quirky the operating system may be. He loves helping contributors write excellent code that they can be proud of; and considers it to be just as much fun as writing it himself.

Getting funding for my work is a dream come true. It will allow me to continue doing the thing I love instead of worrying about whether I should rather invest all that time in a money-earning job with much less positive impact on the world around me.

Jason Newcomb (@Jarcho)

Jason Newcomb (@Jarcho) is a maintainer grantee focusing on Clippy.
He is primarily working on fixing bugs and making it easier to develop and contribute to Clippy. He is also focusing on making the review process as smooth as possible.

Being funded allows me to work on something I care about and want to work on instead of what will get me paid. I'm looking forward to seeing how this will impact Clippy and the Rust project in general.

Conclusion

The contributors presented above will be funded for the next 12 months, though of course we hope that we will be able to extend their support going further, as this program is designed to be for long-term stable maintenance funding. We are very excited about them; each one of them has been with the Project for years, and we are very glad that we can support their maintenance work! All of them have already signed their contracts, so they are already being funded as we speak.

While there are many other Rust contributors who are doing awesome work, and who would also deserve to get proper funding for it, we think that this is a great start. We hope that the awesome work done by the funded maintainers will allow us to promote this program, so that we can fund even more Rust contributors!

We would like to once again sincerely thank everyone who made this possible, especially our sponsors. If you would like to help us fund more maintainers, consider donating to RFMF. You can also sponsor individual Rust contributors directly.

26 Aug 2026 12:00am GMT