问题描述
我正在为(大多数为C风格的)C ++插件SDK编写Rust包装器.插件主机是运行事件循环的图形桌面应用程序.插件通常作为该事件循环的一部分被调用.每当这种情况发生时,该插件便具有控制权,并且可以调用任意主机功能.
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.
我要包装的一个C函数返回一个原始指针.该函数返回后,立即保证该指针是有效的C字符串,因此可以安全地取消引用它.但是,在插件回调返回(从而将控制权交还给主机)之后,指针可能会变得过时.我如何为此编写一个符合人体工程学的函数包装,在某些时候不会导致未定义的行为,例如当使用者在下一个事件循环周期中尝试访问该字符串时?
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:
我可以立即取消引用指针并将内容复制到拥有的 CString
:
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.
pub fn get_string_from_host() -> *const c_char {
unsafe { ffi.get_string() }
}
这只是将问题转移给了消费者.
This just shifts the problem to the consumer.
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.
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();
}
推荐答案
您的问题和评论列出了您的选择.它主要归结为满足其他人的期望,即最不意外的规则.这主张返回一个拥有的 String
.如前所述,所拥有的 String
包含一个副本(除非在循环中被称为gazillion次,否则它对性能的影响可以忽略不计)
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)
我强烈建议您不要使用原始指针和 CStr
-引用解决方案,这是步枪.
I'd strongly advise against the raw-pointer- and CStr
-reference-solutions, which are foot-guns.
个人而言,我将使用闭包,因为这实现了基本情况:访问字符串的代码上下文必须移至字符串所在的位置;我们不能将字符串移动到上下文所在的位置(据我们所知,即使调用者也无法控制).闭包解决方案应该允许您吃蛋糕也可以吃: impl Fn(& CStr)->类型的闭包.T
可以是 | s |s.to_owned()
,如果需要的话,使 with_string_from_host
返回一个副本.
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.
这篇关于有没有一种方法可以强制从特定堆栈帧返回后不使用Rust原始指针?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!