I finally found the cheat-code for disabling the type-checker /s

pub trait __ {
    type __<L, R>: __<__<L, ()> = R>;
    fn transmute<R>(self) -> R;
}
impl<T> __ for T {
    type __<L: __<__<<R::__<R, L> as __>::__<R, ()>, ()> = R>, R: __> = L;
    fn transmute<R: __>(self) -> <R::__<R, Self> as __>::__<R, ()> {
        self
    }
}

It’s safe Rust, so what can go wrong?

fn main() {
    let s: String = vec![65_u8, 66, 67].transmute();
    println!("{}", s); // prints "ABC"
}

(playground)

56 Likes

Bonus round: Disable the trait checker, too!

pub trait __ {
    type __<L, R>: __<__<L, ()> = R>;
    fn debug<R: std::fmt::Debug>(&self);
}
impl<T> __ for T {
    type __<L: __<__<<R::__<R, L> as __>::__<R, ()>, ()> = R>, R: __> = L;
    fn debug<R: __>(&self)
    where
        <R::__<R, Self> as __>::__<R, ()>: std::fmt::Debug,
    {
        println!("{:?}", self);
    }
}

fn main() {
    let v = vec![1, 2, 3, 4, 5];
    generic_without_bounds(&v);
}

fn generic_without_bounds<T>(x: &T) {
    x.debug::<()>(); // who needs trait bounds, anyway?
    // ^^^ prints [1, 2, 3, 4, 5]
}

(playground)

arguable even kind-of useful for some emergency debug-printing if adding T: Debug everywhere would be tedious :thinking:

(Please don't use this in production; this will hopefully stop working in the future. Also, compiler crashes are to be expected if “misused”)

28 Likes

Those are some pieces of art... :slight_smile:

1 Like

Relevent github issue: Typesystem soundness hole involving cyclically-defined ATIT, and normalization in trait bounds · Issue #135011 · rust-lang/rust · GitHub

10 Likes

Was my link in the question mark too subtle? :laughing:

7 Likes

lol, sneaky. I almost didn't notice it until I saw the click count.

4 Likes

Insider meta comment: making hidden links like that more obvious (and a bigger click target) is considered a major positive of the link click counting system.

7 Likes

I love that this is under the "tutorials" category.

12 Likes

If only a language with a "natural fit for provable programming"[1] was used so we were not seeing such bugs being found.


  1. ↩︎

1 Like

It just occured to me now that another (more “reasonable”) way of emergency-debugging like that would be to abuse trait object vtables. If used with non-Debug types, it replaces the compiler crashes with run-time UB&crashes.

trait Trait<T: ?Sized> {
    fn __(&self, x: &T)
    where
        T: std::fmt::Debug,
    {
        println!("{x:?}");
    }
}
impl<T: ?Sized> Trait<T> for () {}

// Look mom, no bounds!
fn debug<T: ?Sized>(x: &T) {
    unsafe { std::mem::transmute::<&dyn Trait<T>, [&[fn(_, _); 4]; 2]>(&())[1][3](&(), x) }
}

fn main() {
    debug(&1);
    debug(&[2, 3, 4]);
    debug("hello world!");
}

(playground)

1
[2, 3, 4]
"hello world!"

(Please keep this in any production code, either, for any longer than a short debugging session; vtable layout is unstable anyway, and even the cases where this works are possibly still technically-UB already. With layout changes this can stop working in the future at any time; and UB at run-time cannot be detected or prevented by any additional checks. At least there are no compiler crashes to be expected here.)

there's a project cve-rs

I don't like that project. Through the ways of the internet is somehow got some spotlight, but AFAIK it doesn’t really contain any novelty, but builds on the oldest type-system unsoundness in the books, which is a known issue for almost a decade longer than cve-rs exists

I mean, I guess if you aren’t up for the thought exercise yourself, reading through cve-rs can help with figuring out why circumventing the borrow checker lets you do essentially anything (e.g. arbitrary transmutations, etc…)

3 Likes

Rust2024 will be released soon, should it get stricter to prevent some "safe" bugs? E.g, it currently allows to get a pointer without unsafe, only dereferencing a pointer needs unsafe.

Producing raw pointers without unsafe is deliberately allowed anyway and not known to be problematic (w.r.t. overall language soundness) in any way.

Getting “stricter” in this sense, is not what editions are for, or something that editions can be helpful with. They explicitly support full interoperability, indefinitely, so requiring unsafe for something in a later edition doesn't actually prevent all that much.

Issues like these (this thread OP, or the GH issue linked above) with actual soundness problems aren't unfixed because of stability concerns anyway, but because they are hard to solve; and when they are solved, they will be fixed in all editions, because Rust’s goals of safety means it allows for itself to apply technically-breaking language changes if they are necessary to fix soundness issues. (And of course whilst also aiming to make such changes as minimal-impact as possible.)

Finally, scope of the 2024 edition is fixed for a few months already. (We don't even have 2024 anymore :sweat_smile:) There is no chance of including any new features in it now. By now, the implementation is also completely done AFAIK… it will be part of beta tomorrow, actually.

There's even another crate[1] that existed 5 years prior, so I always considered that one an "interesting iff you're susceptible to hype" phenomenon. Did manage to get the issue locked though...


  1. which I won't bother to name ↩︎

1 Like

which is also pointed out in the last comment on GitHub before it was locked :slight_smile:


  1. which I won't bother to name ↩︎

Can you optimize HIR and MIR (the intermediate code) to catch these bugs?

4 Likes

Neat! I just got a shiny new badge :scream_cat: :sparkler: :partying_face:

~~ thanks everyone!! :crab:


Great Topic

This badge is granted when your topic gets 50 likes. You kicked off a fascinating conversation and the community loved the lively discussion that resulted!

Oh the… “lively discussion that resulted”? Well, ya can’t have everything :smile_cat:


7 Likes