Soundness of transmuting immutable references to mutable when the underlying data is truly immutable

Hmm, perhaps there's technically no conflict here as I qualified with "unless it makes the job of the programmer or implementation easier" (which is a lot of wiggle room!); but certainly it's interesting that there's an intentional goal and emphasis (at least by you!) to make more programs legal rather than make it easier to prove when a program is legal or not. (Not a judgement, what do I know?)

Perhaps that feeds into my later reply's mentioning that more complicated local rules are a good trade off, as more code that's natural to write is legal so it's reducing the chance you'll trip over UB doing the things you're already doing; while globally you want simpler rules if possible, so you can more easily justify the safety requirements, etc.?

1 Like

Note the "all other things being equal" part of that point. If it was practical, we'd love to make everything either have safe-and-defined behaviour, or be a compile error.

UB is the escape hatch when we don't really have a practical alternative, not something that we want to do. Rust would be way easier to specify if we eliminated unsafe :upside_down_face:

Making more things compile via, say, Angelic non-determinism - Wikipedia , is something we'd rather avoid even though it makes more programs legal because it's hard to write a checker for and it's hard to write a // SAFETY comment for why the unsafe operation will be correct.

But on the other side of things, we'll do things like remove language-level UB for non-UTF-8 str · Issue #71033 · rust-lang/rust · GitHub because demoting that from a validity invariant ("Immediate UB") to a safety invariant ("Library UB") makes more programs correct without really impacting any optimization opportunities.

6 Likes

This is confusing to me because it seems to contradict what others are saying here. According to what you said above, it wouldn't be immediate/instant UB to convert & to &mut, since a problem could only come later when it is used. Yet most people here are saying it is instant UB.

(I'm just curious. I fully intend to always just believe Miri when it says my unsafe code is UB! So the term UB is all I need to know and I can ignore the different types of UB. As @khimru pointed out, the term UB is confusing enough by itself.)


I think I want to retract what I said about instant UB above. When I reread what people have said, it has to do with the compiler behavior and optimization it performs, not with whether the &mut is used later on. I still think the word "instant" is not very descriptive, but I do understand the distinction.

Yeah, it's instant when you write it, not when it runs! Not ideal language.

2 Likes

Since I don't see anyone else directly mentioning this, I thought I would. Even assuming you could resolve the issues with what it means for an immutable reference to be unique (which I think is possible to do), it still has the following negative optimization consequence: If arbitrary functions are allowed to cast a shared reference to a mutable reference (under certain conditions), then you cannot assume the data behind such a reference is unchanged. Concretely:

fn foo(x: &u8) {
  let y: &mut u8 = unsafe { transmute(x) };
  *y += 1;
}

// in another crate, where foo() is opaque:
fn main() {
  let x = 1;
  foo(&x);
  println!("{x}");
}

The compiler would like to optimize the final line to print 1, and is only allowed to do so because it knows that any attempt to do a transmute like what foo is actually doing here would be undefined behavior.

1 Like

Moreover, there is likely some unsafe code that already uses this property for soundness— If the shared reference you’re using has been seen by third-party code (which includes std!), actually modifying the value through your generated &mut will be UB regardless of the soundness of generating it in the first place.

I should have stated "unique and mutable" ( or something ). My language was not at all precise.

I'm not sure I understand. In the example, the shared reference is the unique reference existing for the original value (although the value itself still exists and can have other shared references made to it later, as must always be the case for any shared reference). So by your rules it should be legal to make it into a mutable reference and modify it. If the only thing you can convert into a mutable reference is a reference that is already mutable, I'm not sure what it is accomplishing?