Follow up to "Changing the rules of Rust"
In my previous post, I described the idea of using an edition mechanism to introduce a new
auto trait. I wrote that the compiler would need to create an “unbreakable firewall” to prevent
using !Leak
types from the new edition with code from the old edition that assumes values of all
types can be leaked.
The response has been pretty optimistic that ensuring this would be possible, even though I wrote in the post myself that I “despair” over how difficult it was. I’ve received a great example from Ariel Ben-Yehuda which demonstrates how this problem is more difficult to solve than you would probably think.
This example is going to involve four different crates. The first crate, foo
, is in the 2024 edition, and
defines a trait with a single generic method:
// crate foo; edition = 2024
trait Foo {
fn foo<T>(input: T);
}
The second crate, bar
, is in the 2021 edition, depends on foo
, and implements that trait for its
own type. Its implementation forgets the argument to that method:
// crate bar; edition = 2021
pub struct Bar;
impl foo::Foo for Bar {
fn foo<T>(input: T) {
std::mem::forget(input);
}
}
This code is legal under the firewall rules I listed last time!
The third crate, baz
, is in the 2024 edition. It only depends on foo
, not bar
, and uses the
trait in a new function, using its own private !Leak
type:
// crate baz; edition = 2024
struct Baz;
impl !Leak for Baz { }
pub fn baz<T: foo::Foo>() {
T::foo(Baz);
}
All of the code in foo
and baz
is in the 2024 edition, and all of it is valid, because none of
it involves a Leak
bound or any 2021 edition code. Maybe you can see where this is going..
A fourth crate, quux
, depends on both bar
and baz
:
// crate quux;
pub fn quux() {
baz::baz::<bar::Bar>()
}
This code now leaks a type that doesn’t implement Leak
! baz::baz
will pass its !Leak
type to
bar::Bar::foo
, which will leak it. Somewhere, there must be an error. But where?
Before you start typing your response, consider:
- I avoided saying what edition
quux
is compiling under. It does not matter. If all offoo
,bar
, andbaz
compiled, it must bequux
which doesn’t compile, regardless of what edition it’s compiled under. - The error cannot be in
foo
, because because surely traits with generic methods are not an error and will not become an error in 2024. - The error cannot be in
baz
, because nothing inbaz
is invalid at all: you instantiated a generic interface with a type that meets the bounds of that interface. - Upgrading your crate to a new edition has historically not been considered a breaking change,
except insofar as it raises your MSRV. This is very important for achieving a seamless upgrade
across editions, as everyone will not upgrade at once. If you say the error is in
bar
, for example, the implication is that upgradingfoo
from 2021 to 2024 is a breaking change, because you can implement bothfoo
andbar
exactly as written under edition 2021 today. So the error cannot be inbar
.
It seems like the error just to be in quux
, but none of the types involved in quux
have anything
at all to do with Leak
. What are the rules you are enforcing to make quux
error? And how do you
explain them to users so that they have half a clue what’s going on when they run into this issue in
a practical scenario? Good luck!