Explain this program

fn main(){

          let z:i32 = 44;
          println!("{}",z);

          let f: *mut u32= &z as *const i32 as *mut       i32  as *mut u32  ;
          unsafe{*f=4294967295;};

          println!("{:?}",z);
          println!("{:?}",unsafe{*f});
 //z = 1234u32

}

So here my points would be

1.z is i32 and f is *mut u32
2.but how f points to z? Even both or different types
3. Explain this line. &z as *const i32 as *mut i32 as *mut u32

First of all that program is UB, so the Rust compiler is allowed to print whatever it likes. It is UB because you modify the variable z through a shared reference, which is absolutely not allowed. Also, when you access z for printing after assigning to *f, that invalidates f's borrow, and thus any further uses of it are also UB.

But ignoring that:

2.but how f points to z? Even both or different types

It's because u32 and i32 are guaranteed to have the same layout by Rust; just a sequence of four initialized bytes, aligned on a four-byte boundary. So if you have a four-byte aligned pointer to four valid, initialized bytes, it doesn't matter whether its interpreted as a u32 or an i32, the result is the same.

This creates the following chain of types:

  • &i32: this is a shared reference to a signed 32-bit integer.
  • *const i32: this is a pointer to an immutable signed 32-bit integer.
  • *mut i32: this is a pointer to a mutable signed 32-bit integer.
  • *mut u32: this is a pointer to a mutable unsigned 32-bit integer.

And they are all the same value, as as casts are used to convert between them.

2 Likes

Your program contains undefined behavior. You must not access z through a pointer that’s derived from a shared reference &z. If you run it in miri (e.g. through the “tools” button in the playground), you’ll see

error: Undefined Behavior: no item granting write access to tag <untagged> at alloc1403 found in borrow stack.
 --> src/main.rs:7:9
  |
7 |         *f = 4294967295;
  |         ^^^^^^^^^^^^^^^ no item granting write access to tag <untagged> at alloc1403 found in borrow stack.
  |
  = help: this indicates a potential bug in the program: it performed an invalid operation, but the rules it violated are still experimental
  = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
          
  = note: inside `main` at src/main.rs:7:9
  = note: inside `<fn() as std::ops::FnOnce<()>>::call_once - shim(fn())` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/function.rs:227:5
  = note: inside `std::sys_common::backtrace::__rust_begin_short_backtrace::<fn(), ()>` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/sys_common/backtrace.rs:125:18
  = note: inside closure at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/rt.rs:66:18
  = note: inside `std::ops::function::impls::<impl std::ops::FnOnce<()> for &dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe>::call_once` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/function.rs:259:13
  = note: inside `std::panicking::r#try::do_call::<&dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe, i32>` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panicking.rs:379:40
  = note: inside `std::panicking::r#try::<i32, &dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe>` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panicking.rs:343:19
  = note: inside `std::panic::catch_unwind::<&dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe, i32>` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panic.rs:431:14
  = note: inside `std::rt::lang_start_internal` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/rt.rs:51:25
  = note: inside `std::rt::lang_start::<()>` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/rt.rs:65:5

As for the behavior of the program if you used something like

let mut z: i32 = 44;
println!("{}", z);

let f: *mut u32 = &mut z as *mut i32 as *mut u32;
...

(eliminating the UB but giving the same output on current rustc)

Negative integers for the i32 type are represented using the two’s complement. This means that the bit pattern

11111111 11111111 11111111 11111111

represents 232 − 1 = 4294967295 as a u32, but also −1 as a i32.

2 Likes

You said that in memory i32 and u32 are the same ok. But consider this program.

fn main() {
    let z = 44;
    let f = z as u64;

    let f = &z as *const i32;
    let x = &z as *const i32 as *const u64 as *mut u64;

    unsafe { *x = 4444u64 };
    println!("{:?}", unsafe { *x });
}
  1. f= z as u64 is ok. Allocate 8 bytes and copy the z value in it and assign it to f.

  2. but i dont understand this.

    let x = &z as *const i32 as *const u64 as *mut u64;
    

    My point should be is x points to z.
    But x type is *mut u64. So it wants points to 8 bytes
    But z is only 4 bytes.
    Where are another 4 bytes x points to?

If you have Undefined Behavior anywhere in your program, the compiler is allowed to ignore what you've written. It doesn't have to execute your code any more. It may turn the simple logical behavior you intended into a total joke that makes no sense. It can change your i64 into i0. It can assume variable x doesn't exist. It can delete the whole function.

For example:

pub fn foo() -> u64 {
    let z = 11;
    let x = &z as *const i32 as *const u64 as *mut u64;
    unsafe { *x = 4444u64 };
    unsafe { *x }
}

This function contains UB. It doesn't have to return 4444. It doesn't have to return at all!

It's not a theoretical issue. The optimizer actively checks "is this UB? It is! So I'll just ignore what it says, and do what seems easier/faster here". In the current compiler it happens to return value 11, completely ignoring the reassignment.

If you remove the UB:

pub fn foo() -> u64 {
    let mut z = 11i64;
    let x = &mut z as *const i64 as *mut u64;
    unsafe { *x = 4444u64 };
    unsafe { *x }
}

then it returns 4444 as it should.

4 Likes

I encourage you to look up "buffer overflow".

4 Likes