Lifetimed raw pointers?

is there a way to force a raw pointer to have a non-'static lifetime?

e.g.

pub unsafe extern "C" fn foo<'a>(raw: *mut Lt<'a, Bar>) {
  ...
}

and if so what is the proper way of doing it?

I was going to write something along the same lines and then noticed the documentation already answers how to do this:

The example is for two pointers, but it shouldn't be difficult to adapt :slight_smile:

You might want to add something like [repr(C)] to the wrapper strict to be able to use it instead of the raw pointer itself when communicating with C, as long as the wrapper struct only contains the pointer as its only sized field it should be safe to use instead of the pointer itself.

Edit: according to the rustonomicon the correct repr would be transparent Other reprs - The Rustonomicon

1 Like

indeed but we can't "just use phantomdata" here. because this is an FFI function. (it also has to be #[no_mangle] but we digress.)

we're just not sure where to put the Lt<'a, T> wrapper, and what exactly it should look like:

#[repr(transparent)]
struct Lt<'a, T> {
  t: T,
  _p: PhantomData<&'a T>, // or &'a () or ...
}

fn foo(raw: *mut Lt<'a, T>) // or Lt<'a, *mut T> or ...

Why are you trying to do this?

to force a non-'static lifetime onto the downstream user, to solve an UB-hole.

Any use of raw pointers will always involve unsafe code. Maybe you are looking for a reference?

1 Like

I don't exactly understand:
If your downstream is rust, why not use references? If your downstream is C, how would this help? (As C has no notion of lifetimes)

Edit: I do not think that it is possible to force any kind of lifetime on C at compiletime/runtime. C could always just circumvent whatever restrictions you tried to implement on the type level (or by accessing the internal field of an RC without using the appropriate functions).

1 Like

our crate, hexchat-plugin, is unsound because you can store the owned handles in thread locals and cause use-after-free that way. you're supposed to only be able to store them in your plugin struct. forcing a lifetime would fix that.

can you help us do what we're trying to do, instead of what you think we should be trying to do?

Could you explain what the (high level) communication looks like for the plugin and wherever it is used? I imagine thats where C comes into the picture?

so it's either this, or we use Pin<&'ph UnsafeCell<RawPh<'ph>>> instead of *mut c_void. the annoying thing with that is that we'd have to expose RawPh outside the crate but maybe that's not a big deal.

As far as FFI interface goes: a struct containing a raw pointer and a phantom lifetime should be equivalent to a reference (with the same lifetime). As far as I know this is also what cbindgen produces on the interface.

So in your case you might just want to use

pub unsafe extern "C" fn foo<'a>(raw: &Bar) {
  ...
}

(Change to your original code snippet)

As explained so far, your plugin would not be unsound because storing dangling raw pointers is not unsound and any use of them would require unsafe anyway, so the fault would be in the user who used the raw pointer after it became invalid, not in your library.

Anyway, are you perhaps looking for something like this?

#[repr(transparent)]
struct Handle<'a> {
    ptr: *mut Target,
    lifetime: PhantomData<&'a Target>,
}
1 Like

does this look good?

/// Exports a hexchat plugin.
#[macro_export]
macro_rules! hexchat_plugin {
    ($l:lifetime, $t:ty) => {
        #[no_mangle]
        pub unsafe extern "C" fn hexchat_plugin_init<$l>(plugin_handle: Pin<&$l ::std::cell::UnsafeCell<$crate::RawPh<$l>>>,
                                              plugin_name: *mut *const $crate::c_char,
                                              plugin_desc: *mut *const $crate::c_char,
                                              plugin_version: *mut *const $crate::c_char,
                                              arg: *const $crate::c_char) -> $crate::c_int {
            $crate::hexchat_plugin_init::<$l, $t>(plugin_handle, plugin_name, plugin_desc, plugin_version, arg)
        }
        #[no_mangle]
        pub unsafe extern "C" fn hexchat_plugin_deinit<$l>(plugin_handle: Pin<&$l ::std::cell::UnsafeCell<$crate::RawPh<$l>>>) -> $crate::c_int {
            $crate::hexchat_plugin_deinit::<$l, $t>(plugin_handle)
        }
        // unlike what the documentation states, there's no need to define hexchat_plugin_get_info.
        // so we don't. it'd be impossible to make it work well with rust anyway.
    };
}

Isn't that equivalent to &'a Target?

there's no unsafe API exposed anywhere by hexchat-plugin, so it's all the wrappers (particularly Drop) that make it unsound.

which means it's on us to make it sound.

which is what we're trying to do.

Well, if it's an owned handle then it would need a destructor. You can't do that with references because they are not owned.

If you have no unsafe APIs anywhere, then how do raw pointers come into the picture in the first place?

2 Likes

yeah sure we could take a "why even bother with safe wrappers when you can just make it someone else's responsibility" but also

why

guess what, safe wrappers are designed to bridge safe and unsafe code soundly.

and if we're failing at that we need to fix it.

what even is this argument?!

But that's not what I'm suggesting you do! Rather, the code you posted appears to have raw pointers in the public API, and any such API that does that will make it someone else's responsibility.

If you use a struct like the one I posted, then you don't need any lifetimes on the raw pointers, and you can make a safe API.

no, that's a safe wrapper around an unsafe API.

how else do you safely wrap the entry point for a plugin API?

(as you may know, forbid unsafe doesn't prevent macros from working)

True, but then again if you have an owned handle it usually shouldn't require a lifetime, right?
I guess you could construct a use case where you want to pass ownership to someone but still forcing them to not use it any longer than some specific lifetime? But then again, isn't that just &mut with extra steps?