Rust 生命周期:如何围绕共享库编写包装结构?
我正在尝试围绕Rust中的共享库编写包装器结构:Rust 生命周期:如何围绕共享库编写包装结构?,rust,lifetime,Rust,Lifetime,我正在尝试围绕Rust中的共享库编写包装器结构: pub struct LibraryWrapper u32> } impl{//此处定义的生存期`'lib` pub fn new()->libloading::Result{ 让lib:libloading::Library=libloading::Library::new(lib_路径)?; 让func:libloading::Symbol无法执行此操作,因为您无法确保以后某些代码不会破坏LibraryWrapper结构以保留func并删除l
pub struct LibraryWrapper u32>
}
impl{//此处定义的生存期`'lib`
pub fn new()->libloading::Result{
让lib:libloading::Library=libloading::Library::new(lib_路径)?;
让func:libloading::Symbol无法执行此操作,因为您无法确保以后某些代码不会破坏LibraryWrapper结构以保留func并删除lib。例如:
fn this_is_bad)->libload::Symbol如前所述,已检查的借阅需要确保您不会为了保留函数而破坏某些内容并丢弃库。我已经与此进行了多次斗争,并得出以下结论:
动态链接的libloading
东西是我认为可以变成static
的为数不多的东西之一。如果它是硬编码的so/dll/dylib,我通常会为它创建一个特殊的static,因此需要合理的努力才能删除它。对于编译时加载的未知东西(例如,通过配置文件管理)我通常使用互斥锁保护一个静态
中央注册表,它只是一个HashMap
,带有一个库名或路径键,并且只允许在整个程序终止时删除库。是的,这基本上是一个单例,不,我一般不建议对几乎其他任何东西这样做,这种情况是一个特殊的例外n
为了增加安全性,我建议您隔离静态,只使用函数或结构来引用它,特别是在哈希映射注册表中,因为很容易有人对其执行删除,或者覆盖哈希映射,或者不小心用相同的名称替换库。因为它是静态的,所以仍然允许您将函数存储为如果您真的需要,也可以使用另一个静态/单例,而不必每次都查询库(但我不确定每次都请求它会有多少性能开销,因为还没有对它进行基准测试)
我这样做有几个原因:
您通常希望将函数当作普通库来使用,这与链接FFI或静态拉入另一个库没有什么不同。无论如何,您必须知道函数名,因此实际上与正常使用包含这些函数的文件是一样的
当您链接的库在技术上是动态的,而只是引用一个依赖项时,您不必用'lib
生存期或库函数参数或其他成员来污染您想要引用中的功能的所有内容。如果您的程序变成了一个静态链接的程序,则它最终是不可知的库(只需删除包装静态
动态库的文件中的设置行,并将其更改为指向其他位置的函数)
在整个程序中,您通常只需要/想要/应该链接一次给定的动态库,这与static
的作用非常相似
在程序终止之前,很少有人想删除库,不管是动态库还是非动态库
它隔离了不安全的
初始化和将代码加载到一个文件的函数(当然包装器结构也会处理这个问题)
对于这些原因,我认为最常见的例外是选项4,比如如果你有一个高度可配置的插件系统,如果用户想要禁用旧插件并激活新插件,插件占用的额外内存可能是合理的,在这种情况下,设计包装方案可能是正确的选择,或者在库包装器中添加一个不安全的fn close\u dynamic\u lib\u no\u severy\u do\u you\u really\u希望\u do\u this(self)
我能想到的另一个例外是,如果您正在为crates.io
编写库,您真的希望用户能够管理其动态库并将其提供给您的库(例如,它可能位于非标准路径中,或者他们可以在多个实现之间进行选择),即使这样,修改这种方法可能仍然是有益的。我最终使用了惰性静态板条箱。我认为对于我的用例来说,它比std::sync::Once
更方便。感谢@LinearZoetrope让我想到这个想法!这很有意义。我需要如何更改代码才能使用Rc代码>?简单地将库包装在Rc
中并调用.clone()
没有帮助…我认为在我的情况下使用HashMap
注册表是过分的,因为我只从一个库加载一个函数。Once
这一点在我看来非常适合。谢谢!