Is keeping a raw pointer of a stack allocated variable safe?


#1

Is it safe to call the following function, and then use the returned *const i64 freely?

fn returnraw() -> *const i64 {
    let x: i64 = 5;xp
    &x as *const i64
}

I’m not very good at this low-level things, but I would think x gets deallocated from the stack once the function returns, causing the returned raw pointer to point to a location that might get overwritten; however the following function prints the right value for ‘x’, even tho I try to allocate some variables on the stack after calling the function(which, according to my humble understanding of the stack, should cause the memory pointed to by xp to get overwritten).

#[test]
fn rawpointer() {
    let xp: *const i64 = returnraw();
    let y: i64 = 8;
    let z: i64 = 13;
    println!("x: {}, y: {}, z: {}", unsafe {*xp}, y, z);

}


#2

The compiled version of the function allocates all of its stack space immediately - y and z will live above x on the stack.


#3

You’ve got a dangling pointer, so that xp will be pointing at garbage when you dereference it and print it to the screen. Luckily it’s only being interpreted as an integer and the memory still belongs to your program so you won’t have any overly bad behaviour, but if you returned a *const Vec<u32> instead the results would be completely different.

The rule of thumb with raw pointers is that you should try to follow all the normal rust rules when it comes to borrowing. The borrow checker statically checks for undefined behaviour, whereas with raw pointers that becomes the programmer’s responsibility.

Compiling the non-raw version of this on the playground would give you the normal error about a variable not living long enough.

fn returnraw<'a>() -> &'a i64 {
    let x: i64 = 5;
    &x
}

error[E0597]: `x` does not live long enough
 --> src/main.rs:3:6
  |
3 |     &x
  |      ^ does not live long enough
4 | }
  | - borrowed value only lives until here
  |
note: borrowed value must be valid for the lifetime 'a as defined on the function body at 1:1...

#4

For an example of what can go wrong, take a look at this playground.

fn foo() -> *const i32 {
    let x = 5;
    println!("(&x, x) = {:?}", (&x as *const _, x));
    &x as *const _
}

fn bar() {
    let y = 6;
    println!("(&y, y) = {:?}", (&y as *const _, y));
}

fn main() {
    let xp = foo();
    bar();
    println!("(xp, *xp) = {:?}", (xp, unsafe { *xp }));
}

Here because foo and bar have the same stack layout they end up placing x and y into the same memory location, effectively overwriting the value of x with the value of y. This is very much undefined behaviour though, the optimizer has free reign to rearrange the stack layout in the functions and change the behaviour of this code, which is one of the reasons that dereferencing a raw pointer is unsafe.


#5

Alright thanks, but just to be sure, if I still want to return a *const i64, I should heap-allocate it?

fn returnraw() -> *const i64 {
    let x: Box<i64> = box 5;
    Box::into_raw(x)
}

#6

Heap allocation will mean it gets kept around until it is manually freed, which MUST be done from Rust by “re-assembling” the box with from_raw.

Why do you have the requirement on a raw pointer? Are you doing FFI to some other languages? (This may help us advise you on the proper solution).

The problem with this unsafety, raw pointers and FFI is that it’ll often do the “correct” thing for the wrong reasons when you try it locally. Then, somebody compiles it on a different OS, or with a different version of the compiler, or different optimisation flags, or just during a different phase of the moon, and everything suddenly explodes…


#7

Yes, I’m doing some FFI. I want to create an interface in rust that will be callable from for example nodejs or django. I have a struct Profiler that I want to be able to create instances of and manipulate from another language, I think the best way to do it is to have an init() function that creates a Profiler object and returns a pointer to it, then the caller can pass it back to the Rust program whenever it wants to do something with it. Right now I have the following:

	use profiler::Profiler;

#[no_mangle]
pub extern "C" fn init(threshold: f64) -> *mut Profiler  {
	Box::into_raw(box Profiler::new(threshold))
}

#[no_mangle]
pub extern "C" fn iterate(profiler: *mut Profiler) -> usize {
	unsafe { &mut *profiler }.iterate()
}

#[no_mangle]
pub extern "C" fn rate(profiler: *mut Profiler, rating: f64) {
	unsafe { &mut *profiler }.rate(rating);
}

#[no_mangle]
pub extern "C" fn drop(profiler: *mut Profiler) {
	let _ = unsafe { Box::from_raw(profiler) };
}

I think I’l manage, there are a lot of resources out there for this kind of thing. Right now I’m fiddling with the python side, trying to replicate https://github.com/alexcrichton/rust-ffi-examples/tree/master/python-to-rust.


#8

If you are working with python there’s Pyo3 or rust-cpython you can use to create safe bindings that are callable from both Python and Rust. Otherwise there’s neon if you want to call your Rust code from node. I’d usually recommend those instead of writing the bindings yourself, just because interracting with raw memory and FFI is annoying to do in any language.

If you are looking for snippets of code, there’s also the omnibus. Or you might want to check out Using unsafe for Fun and Profit (shameless plug) if you want a more detailed explanation of the thought process behind doing FFI.

If you find anything lacking or want something explained, feel free to let us know :slight_smile:


#9

Not to mention wildly unsafe, embarrassingly easy to get wrong, and one of the prime arguments for using Rust in the first place :innocent:

Memory management is also highly annoying across language boundaries. Any variable that is allocated by one language must be freed by that language, as the respective memory allocators are not aware of each other. Expect many more of those init/drop pairs!