2

I have an Option<&T> and I would like to have a raw *const T which is null if the option was None. I want to wrap an FFI call that takes a pointer to a Rust-allocated object.

Additionally, the FFI interface I am using has borrowing semantics (I allocate something and pass in a pointer to it), not ownership semantics

extern "C" {
    // Parameter may be null
    fn ffi_call(*const T);
}

fn safe_wrapper(opt: Option<&T>) {
    let ptr: *const T = ???;
    unsafe { ffi_call(ptr) }
}

I could use a match statement to do this, but that method feels very verbose.

let ptr = match opt {
    Some(inner) => inner as *const T,
    None => null(),
};

I could also map the reference to a pointer, then use unwrap_or.

let ptr = opt.map(|inner| inner as *const T).unwrap_or(null());

However, I'm worried that the pointer might be invalidated as it passes through the closure. Does Rust make a guarantee that the final pointer will point to the same thing as the original reference? If T is Copy, does this change the semantics in a meaningful way? Is there a better way that I am overlooking?

Stargateur
  • 24,473
  • 8
  • 65
  • 91
ddulaney
  • 843
  • 7
  • 19
  • 1
    @Stargateur I will _eventually_ use `unsafe` to manipulate the `T` through the pointer. Additionally, the FFI interface I am using has borrowing semantics (I allocate something and pass in a pointer to it), not ownership semantics, so `&` seems like a better choice than `Box`. This question is about constructing the pointer, which is a safe operation. My main question is: how can I make sure the resulting pointer actually points to the same thing as the reference? – ddulaney Mar 24 '19 at 05:31

1 Answers1

3

Yes, this is safe. I'd write it as:

use std::ptr;

fn safe_wrapper(opt: Option<&u8>) {
    let p = opt.map_or_else(ptr::null, |x| x);
    unsafe { ffi_call(p) }
}

If you find yourself writing this a lot, you could make it into a trait and reduce it down to a single method call.

the pointer might be invalidated as it passes through the closure

It could be, if you invalidate it yourself somehow. Because the function takes a reference, you know for sure that the referred-to value will be valid for the duration of the function call — that's the purpose of Rust's borrow checker.

The only way for the pointer to become invalid is if you change the value of the pointer (e.g. you add an offset to it). Since you don't do that, it's fine.

Does Rust make a guarantee that the final pointer will point to the same thing as the original reference?

It depends what you mean by "final". Converting a reference to a pointer will always result in both values containing the same location in memory. Anything else would be deliberately malicious and no one would ever have used Rust to begin with.

If T is Copy, does this change the semantics in a meaningful way?

No. Besides we are talking about a &T, which is always Copy

See also:


the FFI interface I am using has borrowing semantics (I allocate something and pass in a pointer to it), not ownership semantics

To be clear, you cannot determine ownership based purely on what the function types are.

This C function takes ownership:

void string_free(char *)

This C function borrows:

size_t string_len(char *)

Both take a pointer. Rust improves on this situation by clearly delineating what is a borrow and what is a transfer of ownership.

extern "C" {
    // Parameter may be null
    fn ffi_call(*const T);
}

This code is nonsensical; it does not define the generic type T and FFI functions cannot have generic types anyway.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366