4 ms·
It is unsound to transmute `&'a T` into `&'static T`, but it is not UB - as long as all of the subsequent uses of the transmuted reference obey the "real" lifet
by nathanrf 3y ago
It is unsound to transmute `&'a T` into `&'static T`, but it is not UB - as long as all of the subsequent uses of the transmuted reference obey the "real" lifetime of the original reference:
fn example<'a>(r: &'a mut i32) -> &'static mut i32 {
unsafe { std::mem::transmute(r) }
}
fn main() {
let mut x: i32 = 5;
let ptr: &'static mut i32 = example(&mut x);
*ptr = 6;
println!("{x}");
}
(because it's unsound, it's considered wrong to do this - you should not intentionally write functions whose types are lies, and this one definitely lies, so it should be marked `unsafe` - but this is not automatic UB)
https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=978e04a0df4d8c191533674fbe358a39 https://play.rust-lang.org/?version=stable&mode=debug&editio...
You can run through Miri and confirm there's no UB even though we're modifying `ptr`, whose lifetime has been extended beyond the length of the function.
However, Rust does have extra guarantees here which make this irrelevant to the pessimization problem in the linked article - you cannot ever legally convert a `&T` into a `&mut T` - this is always UB. This means that Rust guarantees that `example` does not modify `x` (unless e.g. it contains an `UnsafeCell`, like a `Mutex`'s contents), and so it does not need to defensively reload its value.
That is to say: Rust, just like C++, makes it legal (but frowned upon) to "leak" a reference beyond the stated lifetime it's provided as. But unlike C++, it is (always!) illegal to "upgrade" a `&T` into a `&mut T`, and thus the fact that it escapes does not hinder other optimizations.
- sapiogram 3y agoThanks! Do you know if the Rust compiler is able to pass this information to LLVM somehow?
- hkalbasi 3y agoIt seems it isn't yet: https://github.com/rust-lang/rust/issues/116744 https://github.com/rust-lang/rust/issues/116744
- deleted 3y ago[deleted]
- afdbcreid 3y agoIt does pass this information to LLVM, in the form of the `readonly` attribute. This seems to be a bug in LLVM that does not optimize the function propely, I don't know why.
- kaba0 3y agoCould you please expand on this last point? Would it not be the same in case of C++’s `const`s?
- nathanrf 3y agoIn C and C++, `const` on pointers/references is basically just a comment to programmers - it is part of the type, but doesn't "mean" anything to the abstract machine; the rules don't treat const / regular references/pointers differently, they just say that the types only let you mutate through a mutable pointer. Obviously, good code should treat it as more than just a comment - using `const` correctly clarifies intent and makes it possible to stay sane as a C++ developer, but the abstract machine doesn't care. In C++, you can basically always `const_cast` a `const T&` into a `T&` and then modify it without causing UB. A function that accepts a `const T&` is just pinky promising that it will be polite and probably not do that. It is only UB if the underlying object is "actually const", and even then, it doesn't cause UB until you actually perform the mutation; creating the mutable reference itself is perfectly fine. For example, the following is perfectly legal: int& upgrade_to_mut(const int& x) { return *const_cast<int*>(&x); } int x = 5; const int& x_ref = x; int& x_ref_mut = upgrade_to_mut(x_ref); x_ref_mut = 6; it's only invalid if the object that is pointed at is const, as in: int& upgrade_to_mut(const int& x) { return *const_cast<int*>(&x); } const int y = 5; const int& y_ref = y; int& y_ref_mut = upgrade_to_mut(y_ref); // it is actually legal to produce y_ref_mut, but we cannot modify it y_ref_mut = 6; // this is UB: cannot modify a const object 'y' The difference is that in Rust, "mutation capabilities" are part of references, and so you cannot create them out of nowhere, that would be UB. But in C++, mutation capabilities are part of the object being pointed at, so as long as they happen to be there when you perform the mutation (e.g. you're not modifying a string literal or a variable declared `const`) then there's no problem.
- deleted 3y ago[deleted]
- j16sdiz 3y agoI don't agree it is just a comment to programmer. CppReference says, > Modifying a const object through a non-const access path and referring to a volatile object through a non-volatile glvalue results in undefined behavior. and both compiler have tried to take advantage of this in the past.