I was experimenting with function pointer magic in Rust and ended up with a code snippet which I have absolutely no explanation for why it compiles and even more, why it runs.>
This is "working" because fn() {foo}
and the first closure are zero-sized types. Extended answer:
If this program ends up executed in Miri (Undefined behaviour checker), it ends up failing because NULL pointer is dereferenced. NULL pointer cannot ever be dereferenced, even for zero-sized types. However, undefined behaviour can do anything, so compiler makes no promises about the behavior, and this means it can break in the future release of Rust.
error: Undefined Behavior: memory access failed: 0x0 is not a valid pointer
--> src/main.rs:7:28
|
7 | let closure = unsafe { &mut *closure_ptr };
| ^^^^^^^^^^^^^^^^^ memory access failed: 0x0 is not a valid pointer
|
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
= note: inside `caller::` at src/main.rs:7:28
note: inside `create::` at src/main.rs:13:5
--> src/main.rs:13:5
|
13 | func_ptr();
| ^^^^^^^^^^
note: inside `main` at src/main.rs:17:5
--> src/main.rs:17:5
|
17 | create(foo);
| ^^^^^^^^^^^
This issue can be easily fixed by writing let closure_ptr = 1 as *mut F;
, then it will only fail on line 22 with the second closure that will segfault.
error: Undefined Behavior: inbounds test failed: 0x1 is not a valid pointer
--> src/main.rs:7:28
|
7 | let closure = unsafe { &mut *closure_ptr };
| ^^^^^^^^^^^^^^^^^ inbounds test failed: 0x1 is not a valid pointer
|
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
= note: inside `caller::<[closure@src/main.rs:22:12: 22:55 val:&i32]>` at src/main.rs:7:28
note: inside `create::<[closure@src/main.rs:22:12: 22:55 val:&i32]>` at src/main.rs:13:5
--> src/main.rs:13:5
|
13 | func_ptr();
| ^^^^^^^^^^
note: inside `main` at src/main.rs:22:5
--> src/main.rs:22:5
|
22 | create(|| println!("This will seg fault: {}", val));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Why it didn't complain about foo
or || println!("Okay...")
? Well, because they don't store any data. When referring to a function, you don't get a function pointer but rather a zero-sized type representing that specific function - this helps with monomorphization, as each function is distinct. A structure not storing any data can be created from aligned dangling pointer.
However, if you explicitly say the function is a function pointer by saying create::
then the program will stop working.