Is there a way to enforce that a Rust raw pointer is not used after returning from a specific stack frame?

那年仲夏 提交于 2021-02-05 06:43:27

问题


I'm writing a Rust wrapper for a (mostly C-style) C++ plug-in SDK. The plug-in host is a graphical desktop application that runs an event loop. The plug-in is regularly called as part of that event loop. Whenever this happens, the plug-in has control and can call arbitrary host functions.

One C function which I want to wrap returns a raw pointer. Right after that function returns, the pointer is guaranteed to be a valid C string, so it is safe to dereference it. However, after the plug-in callback returns (thus giving back control to the host), the pointer can become stale. How can I write an ergonomic function wrapper for this which will not result in undefined behavior at some point, e.g. when the consumer tries to access the string in the next event loop cycle?

I've thought about the following approaches:

1. Return an owned string

I could immediately dereference the pointer and copy the content into an owned CString:

pub fn get_string_from_host() -> CString {
    let ptr: *const c_char = unsafe { ffi.get_string() };
    unsafe { CStr::from_ptr(ptr).to_owned() }
}

This is presumptuous — maybe the consumer of my wrapper is not interested in getting an owned string because they just want to make a comparison (that's even the primary use case I would say). Copying the string would be a total waste then.

2. Return the raw pointer

pub fn get_string_from_host() -> *const c_char {
    unsafe { ffi.get_string() }
}

This just shifts the problem to the consumer.

3. Return a CStr reference (unsafe method)

pub unsafe fn get_string_from_host<'a>() -> &'a CStr {
    let ptr: *const c_char = ffi.get_string();
    CStr::from_ptr(ptr)
}

This is unsafe because the lifetime of the reference is not accurate. Accessing the reference at a later point in time can result in undefined behavior. Another way of shifting the problem to the consumer.

4. Take a closure instead of returning something

pub fn with_string_from_host<T>(f: impl Fn(&CStr) -> T) -> T {
    let ptr: *const c_char = unsafe { ffi.get_string() };
    f(unsafe { CStr::from_ptr(ptr) })
}

pub fn consuming_function() {
    let length = with_string_from_host(|s| s.to_bytes().len());
}

This works but really needs getting used to.


None of these solutions are really satisfying.

Is there a way to make sure a return value is used "immediately", meaning that it is not stored anywhere or never escapes the caller's scope?

This sounds like a job for references/lifetimes, but I'm not aware of any lifetime annotation which means something like "valid just in the current stackframe". If there would be, I would use that (just for illustration):

pub fn get_string_from_host() -> &'??? CStr {
    let ptr: *const c_char = unsafe { ffi.get_string() };
    unsafe { CStr::from_ptr(ptr) }
}

pub fn consuming_function() {
    // For example, this shouldn't be possible in this case
    let prolonged: &'static CStr = get_string_from_host();
    // But this should
    let owned = get_string_from_host().to_owned();
}

回答1:


Your question and the comments lay out your options. It mostly comes down to meeting other people's expectations, that is, the rule of least surprise. This argues for returning an owned String. As it was said before, the owned String involves a copy (which will have a negligible performance-impact unless called a gazillion times in a loop)

I'd strongly advise against the raw-pointer- and CStr-reference-solutions, which are foot-guns.

Personally, I'd go with the closure, as this implements the basic situation: The context of the code accessing the string has to move to where the string is; we can't allow the string to move to where the context is (which as far as we can know even the caller may not control). The closure-solution should allow you to have your cake and eat it too: The closure of type impl Fn(&CStr) -> T can be |s| s.to_owned(), making with_string_from_host return a copy if so desired.



来源:https://stackoverflow.com/questions/61106587/is-there-a-way-to-enforce-that-a-rust-raw-pointer-is-not-used-after-returning-fr

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!