10 Oct 2026
Planet Mozilla
Niko Matsakis: Why 'externalized' proofs of cyclic trait impls does not work
For this post, I wanted to talk about two different approaches to handling supertraits. I'm calling them modular proofs vs external proofs. The key idea of this post is that, if we want to have cyclic trait impls, we really need to use a modular proof strategy, where the impl establishes all supertraits hold. Previously we had considered an external strategy, where the piece of code using the impl has the obligation to prove the supertraits hold. Modular proofs always seemed better but I did not think they were workable in the past. But I have become convinced that external proofs are incompatible with Rust as designed, and hence modular proofs are really the only option1. This post dives into that reasoning, and also gives a bit of explanation of what I mean by proofs in the first place.
Traits and supertraits
So what do I mean by modular vs external proofs? Well, it all comes down to who is responsible for proving that supertrait obligations hold. Consider a trait like Magic:
trait Magic: Copy { }
The supertrait declaration means that, whenever X: Magic for some type X, it should be true that X: Copy. We make use of this in generic functions:
fn is_copy<T: Copy>() {
}
fn is_magic<T: Magic>() {
// Legal, because `T: Magic` implies `T: Copy`
is_copy::<T>();
}
The trick is that the compiler has to make sure that this implication holds - i.e., for every type X that implements Magic, X also implements Copy. So how does it do it?
Modular proofs: the impl must show supertraits hold
The obvious answer is to make proving supertraits part of deciding whether an impl is valid. For any impl of Magic, we can require that the Copy supertrait holds. So an impl like this would be illegal:
// In a modular system, this impl is *illegal*
impl Magic for String { }
This impl is illegal because it would require that String: Copy, and that does not hold. Seems good.
Modular proofs are a bit tricky
I am calling these proofs modular because the idea is that we can prove an entire program is valid by proving each part of it separately. In "programming language" theory, this is typically called a "modular" check, as it works by breaking up the entire program into modules that can be independently checked.
The idea with a modular proof is that we can trust impls to show that the supertrait relationships hold, we don't have to go and re-prove them over and over. If the impl is wrong, the impl will be invalid, but our code is fine. So if we have impl Magic for String, that implies the rest of the program can prove that String: Magic:
fn string_is_magic() {
// Legal, because there is an impl for `String: Magic`:
is_magic::<String>();
}
In fact, since we know that Magic implies Copy, the rest of the program can even rely on impl Magic for String to conclude that String: Copy:
fn string_is_copy() {
// Legal, because there is an impl for `String: Magic`,
// and `Magic` implies `Copy`:
is_copy::<String>();
}
So long as impl Magic for String is invalid, none of this poses a problem to soundness, since the program overall doesn't type-check.
Comparison with functions
An easy way to understand the idea of modular checks is to think of functions. Imagine you have a function like this one:
fn compute_sum(a: i32, b: i32) -> i32 {
format!("{a} + {b}") // <-- Error
}
Clearly, this function is not legal. It takes two integers and promises to return a third integer, but in fact it returns a String. So the function is illegal. But if you have a call to that function from elsewhere, we consider that other call to be legal:
fn use_sum() {
let c: i32 = compute_sum(2, 20); // OK
}
Here, use_sum is relying on compute_sum to obey its contract. It's not the job of use_sum to check that, it can just assume it is true.
The catch: how do we decide the impl is invalid
There is a bit of a catch though. How do we decide if the impl is invalid? The basic idea was that impl Magic for String would have to prove that String: Copy. But we just saw that it could, in fact, do that by using itself. In other words, if we aren't careful, we can provide a proof that String: Copy like…
String: CopybecauseMagicimpliesCopyandString: Magicbecauseimpl Magic for Stringexists
and then we would (incorrectly) conclude that the impl is valid. So clearly we need to do something to rule that out. We need a rule that says, when we are proving that an impl is valid, that proof cannot recursively rely on the impl itself.[^termination] I'll come back in a future post to ways we might do that, but for now, I want to explore another alternative.
External proofs: the user of the impl must show supertraits hold
When we first looked at this problem, way back in 2018 or so, we thought of another approach. What if we said that an impl is not responsible for proving supertraits. Instead, the idea would be that impl Magic for String is not enough to say that String: Magic. It only says that Shallow(String: Magic) - i.e., String implements Magic in a shallow way, but not in a deep way that includes the full supertraits. To prove that String: Magic, we have to show that Shallow(String: Magic) and Shallow(String: Copy):2
Shallow(String: Magic)
Shallow(String: Copy)
---------------------------- Magic fully implemented
String: Magic
This has the somewhat counterintuitive implication that impl Magic for String is actually legal in an "external proof" approach:
// In an external system, this impl is LEGAL
// (but unusable)
impl Magic for String { }
The saving grace is that, while this impl is legal, you can't actually use it. This function for example does not compile:
fn string_is_magic() {
// NOT legal in an external system:
// * We can prove that `Shallow(String: Magic)`
// * We CANNOT prove that `Shallow(String: Copy)`.
is_magic::<String>();
}
Here, String: Magic doesn't hold even though there is an impl of Magic for String, because the caller also has to check that String: Copy is implemented, and it is not. Huh, interesting.
Comparison to functions: external is awkward
the "external proof" approach for impls is clearly a bit awkward. If we make the comparison to functions, it's as if the caller has to double check that the callee's body matches its return type, it can't actually trust the declared signature. But, awkward or not, it does resolve our problem: given impl Magic for String, we cannot prove String: Copy, and hence we cannot prove that String: Magic. We can only prove that Shallow(Magic: String), which doesn't imply that the supertraits hold.
But external doesn't work with unsafe traits
Based on the above, for a long time, I was working with the assumption that, weird as they are, we would go with the "external proof" approach. However, as Ralf Jung and lcnr pointed out to me recently, this is very challenging to reconcile with unsafe traits. Consider an unsafe trait like Nullable:
// A type that can be safely transmuted from `0_usize`.
unsafe trait NullWord { }
The way that Rust works, when we write an unsafe impl, it is the job of that impl to prove that the unsafe conditions hold. Other parts of the program get to trust the impl. So if I write a function like this one, it should be considered safe:3
fn foo<T: NullWord>() -> T {
std::mem::transmute(0_usize)
}
Now imagine that I wrote an invalid impl like this one:
// INVALID: We are asserting that `Box` can be null,
// which is not true!
unsafe impl<T> Nullable for Box<T> { }
Given this program I could clearly call foo::<Box<u32>>(), but that would "go wrong" (cause "undefined behavior"). I think we would all agree that the fault lies in the impl. And yet, that is inconsistent: we say that the impl alone cannot be trusted to figure out if the supertraits are implemented, but it can be trusted to figure out if the unsafe impl is valid?
Conclusion
I definitely believe that we want to treat the "extra conditions indicated by unsafe" as a more general version of the other obligations that an impl has to establish to show that the trait holds- and therefore that we must have modular proofs. That's kind of a relief, because something always felt wrong about external proofs, but it was hard to put my finger on a concrete problem. In the next post in this series (whenever that may be…), I expect to cover the approach to coinductive modular proofs that I landed on. Then I expect to talk about an alternative that was proposed to me that I find quite appealing.
-
I think this was obvious to Ralf Jung from the start. But it took me a bit. ↩︎
-
This notation is called an inference rule. The conditions above the line are the premises and the bottom line is the conclusion. It says that, if you know the premises are true, you can infer that the conclusion holds. ↩︎
-
In point of fact, I believe this will not compile because of special rules about unsafe, but that's not relevant to the point I'm trying to make. ↩︎
10 Oct 2026 3:23pm GMT
09 Oct 2026
Planet Mozilla
Firefox Tooling Announcements: Firefox DevTools MCP 0.10.5 released
New release of Firefox DevTools MCP. Available on npm at https://www.npmjs.com/package/@mozilla/firefox-devtools-mcp
Fixed
set_firefox_prefsandget_firefox_prefswork again. They now use WebDriver BiDi to run in the privileged contextlist_pagesno longer fails when the URL or title of a tab cannot be read. That tab is listed with(unknown)values instead
Changed
take_snapshotand the UID-based input tools now use WebDriver BiDi instead of WebDriver Classic- The snapshot script now runs in a sandbox, so page scripts cannot interfere with it and it does not add globals to the page
- The Cursor plugin manifest is replaced by the root
plugin.jsonandmcp.jsonfiles from the Agent Plugins 1.0 standard, which cursor.directory reads
Full changelog: Release v0.10.5 · mozilla/firefox-devtools-mcp · GitHub
Repository and issues: GitHub - mozilla/firefox-devtools-mcp: Model Context Protocol server for Firefox DevTools - enables AI assistants to inspect and control Firefox browser through WebDriver BiDi · GitHub
Public chatroom: https://chat.mozilla.org/#/room/#firefox-devtools-mcp:mozilla.org
1 post - 1 participant
09 Oct 2026 2:03pm GMT
08 Oct 2026
Planet Mozilla
Mozilla Privacy Blog: Canada’s bill C-22 threatens encryption, users’ privacy and the security of digital economies
Encryption is woven into everyday digital life. It protects our messages and passwords, but also banking and payments, health data, government services and the digital infrastructure societies rely on every day. Yet across jurisdictions, governments are increasingly considering laws that expand lawful access to data for law enforcement and national security purposes. In practice, this can mean requiring companies to create new ways to access encrypted data, introduce technical capabilities that bypass existing protections, or make otherwise secure systems insecure. These measures can weaken the very security encryption is designed to provide, with consequences for cybersecurity, privacy, fundamental rights and the wider economy. Canada's Bill C-22 is the latest example of this trend, raising serious concerns about the security and privacy consequences of expanding government access to data.
For Mozilla, protecting that security is fundamental to both the products we build and the principles we advocate for. One of the foundational principles that guide Mozilla's mission and work holds that individuals' security and privacy on the internet are fundamental and must not be treated as optional. Protecting people's privacy and security is not an aspiration for us, but shapes the products we build every day: Firefox blocks trackers, protects you from profiling via cookies and fingerprinting, comes with malware protection and a built-in VPN, offers HTTPs-only mode and protects your passwords and credit card information by encrypting them.
Some of these protections are being threatened by Canada's Lawful Access Act, also known as bill C-22. Part two of the bill, the "Supporting Authorized Access to Information Act" (SAAIA) would introduce sweeping new powers to require electronic service providers to build and maintain capabilities that facilitate government access to information. If passed in its current form, companies could be asked to introduce vulnerabilities, build backdoors, bypass, weaken or otherwise defeat encryption, access data before encryption or decrypt encrypted data to provide access to law enforcement. Service providers could also be compelled to retain and access data they have purposefully chosen not to collect.
Let us be clear: there is no safe way to create exceptional access to encrypted data that only the intended actor can use. An access mechanism created for law enforcement can also be discovered, exploited or abused by potentially bad actors.
Such vulnerabilities and backdoors do not only undermine people's fundamental rights to privacy and data protection, but also the trust and security premises societies everywhere depend on. The same encryption that protects a private conversation also protects financial transactions, sensitive health information, business systems and critical digital services.
Expanding capabilities of AI systems are only exacerbating these risks - governments should encourage the disclosure and patching of vulnerabilities, not compel companies to introduce insecurities deliberately. Only trustworthy and transparently governed digital infrastructures can be the basis for digitally sovereign societies.
We are also concerned by C-22's scope, which does not stop at Canadian services or users. C-22 expansive surveillance capabilities and data retention obligations would undermine people's privacy and security everywhere, and the bill's confidentiality requirements would make it impossible for services to inform their users about security breaches or backdoors introduced.
Guided by our Surveillance Principles for a Secure, Trusted Internet, we call on Canadian policymakers not to rush the legislative process to take experts' feedback into account in amending C-22 to protect everyone's security and privacy. Canada's interests are best served by regulation that protects encryption, strengthens cybersecurity and emphasizes transparency, checks and balances.
The post Canada's bill C-22 threatens encryption, users' privacy and the security of digital economies appeared first on Open Policy & Advocacy.
08 Oct 2026 9:35am GMT