如何解决通过暂时假装它是“静态”来伪造引用的最安全方法是什么?
我的处境不太好,不得不假装一生。看起来有点像这样:
struct Bar<'a> {
cr: &'a mut char,}
fn foo<D,F>(data: D,f: F)
where
D: 'static,// <-- !!!
F: FnOnce(D),{ ... }
let mut c = '⚠';
let bar = Bar { cr: &mut c };
foo(???,|c| /* I need access to a `Bar` here! */);
我必须调用奇怪的函数 foo
。在传递给它的闭包中,我需要访问通过 Bar
传递的 foo
(具有任何生命周期)。 (我知道在这个最小的例子中,我可以直接访问 bar
,因为闭包可以访问它们的环境,但让我们假设这里不可能。)不幸的是,foo
需要 D: 'static
。
当然,在现实中一切都更加复杂。我知道 Bar
和 foo
没有太大意义,但这是我尝试将问题分解为最小示例的尝试。
我如何使这项工作?我相信可以安全地完成这项工作(即没有未定义的行为),但我确定它需要 unsafe
关键字。
基本思想是将 Bar<'not_static>
临时转换为 Bar<'static>
,然后确保它的寿命不会超过原始 c
。我想知道如何最好地做到这一点。我的想法如下:
let mut c = '⚠';
let bar = Bar { cr: &mut c };
let bar_static: Arc<Bar<'static>> = unsafe {
let extended = mem::transmute::<Bar<'_>,Bar<'static>>(bar);
Arc::new(extended)
};
foo(bar_static.clone(),|bar| println!("{}",bar.cr));
if Arc::strong_count(&bar_static) != 1 && Arc::weak_count(&bar_static) != 0 {
eprintln!("bad!");
std::process::abort();
}
这个想法是在调用 c
后动态检查是否不再存在对 foo
的引用(除了我们持有的那个)。这应该可以防止 foo
将 data
存储在静态变量或类似的东西中。我不期望它,但我宁愿结束整个过程,而不是在我的程序中存在内存不安全。
有了这个,我想,我可以确保参考(不正确的 'static
)不会比实际数据长。但是:
- 这种推理合理吗?有意义吗?
- 让我担心的是 rustc 不认为
c
是在unsafe
块之后借用的。我可以(在我的函数中)任意访问c
,尽管存在指向它的引用。这是“只要我不实际访问c
”的情况吗?或者更确切地说是那些“即时UB”案例之一? -
mem::transmute
是适合这项工作的工具,还是我应该使用指针强制转换或其他方式? - 有什么更好的想法吗?
解决方法
答案是没有安全的方法可以改变对象的生命周期。生命周期是编译器保证该值在其使用期间始终有效。
最安全的方法是更改 foo
以包含 data: D
的生命周期。
fn foo<'a,D,F>(data: D,f: F)
where D: 'a,F: FnOnce(D),{
f(data);
}
另一种方法是通过 Bar
更改 Arc<Mutex<char>>
及其使用方式并将其降级为 Weak<Mutex<char>>
。
struct Bar {
c: Weak<Mutex<char>>,}
let t = 't';
let t = Arc::new(Mutex::new(t));
let bar = Bar { c: Arc::downgrade(&t) };
foo(bar,|b| {
if let Some(c) = b.c.upgrade() {
let mut c = c.lock().unwrap();
println!("{}",*c);
*c = 'b';
}
});
println!("{}",t.lock().unwrap());
但是,如果您无法更改 foo
或 Bar
并且您确定引用的对象不会被删除,那么您可以使用 std::mem::transmute
来更改生命周期你的对象。
如文档中所述:
transmute
非常不安全。有很多方法可以使用此函数导致未定义的行为。 transmute
应该是绝对的最后手段。
unsafe fn to_static<'a>(r: Bar<'a>) -> Bar<'static> {
std::mem::transmute::<Bar<'a>,Bar<'static>>(r)
}
let mut t = 't';
let bar = Bar { c: &mut t };
let static_bar = unsafe { to_static(bar) };
foo(static_bar,|b: Bar| {
println!("{}",b.c);
*b.c = 'b';
});
println!("{}",t);
上面的例子之所以有效,是因为 t
位于调用 foo
的范围内,而且我们知道 t
是静态的,因此使用起来完全没问题。但是,如果 foo
将 static_bar
发送到另一个线程,并且 t
不是静态的,那么结果是未定义的行为。
如果您能保证 foo
会在它被调用的范围结束之前完成,那么您可以使用 Mutex
来执行以下操作。
let t = 't';
let t = Arc::new(Mutex::new(t));
{ // create scope here
let mut tlock = t.lock().unwrap(); // MutexGuard<char>
let t_ref_mut = &mut *tlock; // get &mut char -- lifetime of mutex guard
let bar = Bar { c: t_ref_mut };
let static_bar = unsafe { to_static(bar) };
// foo must be guaranteed to complete before the scope ends.
foo(static_bar,|b| {
println!("{}",b.c);
*b.c = 'b';
});
} // MutexGuard goes out of scope and lock should be released
// sanity check that lock is released and value is altered
println!("{}",t.lock().unwrap());
可以查看完整的源代码here
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。