&mut T
and &mut T
results in a compilation error; this is great, it's objectively wrong to borrow mutably twice.
Is *mut T
and*mut T
undefined behaviour or is this a perfectly valid thing to do? That is, is mutable pointer aliasing valid?
What makes it even worse is that &mut T
and *mut T
actually compiles and works as intended, I can modify a value through the reference, the pointer, and then the reference again... but I've seen someone say that it's undefined behaviour. Yeah, "someone said so" is the only information I have.
Here's what I tested:
fn main() {
let mut value: u8 = 42;
let r: &mut u8 = &mut value;
let p: *mut u8 = r as *mut _;
*r += 1;
unsafe { *p += 1; }
*r -= 1;
unsafe { *p -= 1; }
println!("{}", value);
}
and of course, the main point of question:
Note — Thanks to trentcl for pointing out this example actually causes a copy when creating p2
. This can be confirmed by replacing u8
with a non-Copy
type. The compiler then complains about a move. Sadly, this does not get me closer to the answer, only reminds me that I can get unintended behaviour without it being undefined behaviour, simply because of Rust's move semantics.
fn main() {
let mut value: u8 = 42;
let p1: *mut u8 = &mut value as *mut _;
// this part was edited, left in so it's easy to spot
// it's not important how I got this value, what's important is that it points to same variable and allows mutating it
// I did it this way, hoping that trying to access real value then grab new pointer again, would break something, if it was UB to do this
//let p2: *mut u8 = &mut unsafe { *p1 } as *mut _;
let p2: *mut u8 = p1;
unsafe {
*p1 += 1;
*p2 += 1;
*p1 -= 1;
*p2 -= 1;
}
println!("{}", value);
}
Both yield:
42
Does this imply that two mutable pointers pointing to the same location and being dereferenced at different times is not undefined behaviour?
I don't think testing this on compiler is a good idea to begin with, as undefined behaviour could have anything happen, even printing 42
as if nothing is wrong. I mention it anyway as this is one of things I tried, hoping to get an objective answer.
I have no clue how to write a test that could force erratic behaviour that would make it dead obvious that this doesn't work because it's not used as intended, if that's even possible to do so.
I'm aware that this is very likely to be undefined behaviour and break in a multithreaded environment no matter what. I would expect a more detailed answer than that, though, especially if mutable pointer aliasing IS NOT undefined behaviour. (This would in fact be awesome, because while I use Rust for reasons like everyone else - memory safety, to say the least... I expect to still retain a shotgun that I could point anywhere, without it being locked onto my feet. I can have aliased "mutable pointers" without blowing my feet off in C.)
This is a question about whether I can, not about whether I should. I want to dive head-on into unsafe Rust, just to learn about it, but it feels like there's not enough information unlike in "horrible" languages like C about what's undefined behaviour and what's not.
unsafe
, so just creating them must be safe, by definition. Using them is another matter, of course... – Allhallowmasp1
andp2
don't alias. proof – Desensitizep2 = p1
like you would do naturally... And then they point to the same memory location, like expected. I don't really understand that low level kind of stuff, but this is completely wrong. It copies the value and then...? – Preventerunsafe { *p1 }
returns a copiedu8
and&mut
takes a reference to the copy. You could write the same thing with nounsafe
or raw pointers at all, although it's obviously not what you intended. – Desensitize&mut
reference to*p
in order to perform+=
on it. And yeah, you can't "just" move a (non-Copy
) type out of a*mut
pointer, because doing so is even more unsafe than just dereferencing the thing -- you need to useptr::read
for that. – Desensitize&mut and *mut
, and one for*mut and *mut
. Also, strict aliasing wasn't on my mind at all, but if what you say is true, although it's not something I'd be willing to use, especially after learning not to break it in C++, I'm happy to learn that it's an option. – Preventerunsafe { &mut *p1 }
is different from&mut unsafe { *p1 }
. The unsafe block turns the place expression into a value expression, thereby triggering a move. – Selfwill*const T
and *mut T` raw pointers?. – Orchestral