fak*_*ake 6 contravariance lifetime variance rust
我有一个函数需要一个短暂的对象。我希望我总是能够将一个长寿的对象传递给它。但当我尝试对其进行编码时,出现了一个奇怪的错误:
type F<'arg> = Box<dyn FnOnce(&'arg ())>;
fn contravar<'small, 'large: 'small>(f: F<'small>) -> F<'large> {
f
}
Run Code Online (Sandbox Code Playgroud)
特别:
error: lifetime may not live long enough
--> src/lib.rs:3:5
|
2 | fn contravar<'small, 'large: 'small>(f: F<'small>) -> F<'large> {
| ------ ------ lifetime `'large` defined here
| |
| lifetime `'small` defined here
3 | f
| ^ function was supposed to return data with lifetime `'large` but it is returning data with lifetime `'small`
|
= help: consider adding the following bound: `'small: 'large`
Run Code Online (Sandbox Code Playgroud)
它的论证似乎F是不变的,但我猜它是逆变的。我错过了什么吗?有没有办法让F<'arg> 真正逆变'arg?
编辑:看起来“问题”是 Rust 希望以相同的方式对待所有通用特征(包括 Fn/FnMut/FnOnce)。我的观点是,这三个是并且应该被特殊对待,特别是考虑到它们是引用闭包的唯一方式。因此我打开了一个问题
Rust Reference 的关于子类型和方差的页面记录了从 Rust 1.63.0 开始,fn(T) -> ()在 上是逆变的T,在 上dyn Trait<T> + 'a是不变的T。
FnOnce、FnMut和Fn是特征,因此dyn FnOnce(&'a ())不幸的是,这意味着在 上是不变的&'a ()。
// Compiles
pub fn contravariant<'a, 'b: 'a>(x: fn(&'a ())) -> fn(&'b ()) { x }
// Doesn't compile
pub fn contravariant2<'a, 'b: 'a>(x: Box<dyn FnOnce(&'a ())>) -> Box<dyn FnOnce(&'b ())> { x }
Run Code Online (Sandbox Code Playgroud)
有没有办法以
FnOnce某种方式包装以使编译器相信正确的方差?
这是我可以使用代码得出的结果unsafe。请注意,我不保证这是否合理。我不知道有什么方法可以在没有unsafe代码的情况下做到这一点。
use std::marker::PhantomData;
trait Erased {}
impl<T> Erased for T {}
pub struct VariantBoxedFnOnce<Arg, Output> {
boxed_real_fn: Box<dyn Erased + 'static>,
_phantom_fn: PhantomData<fn(Arg) -> Output>,
}
impl<Arg, Output> VariantBoxedFnOnce<Arg, Output> {
pub fn new(real_fn: Box<dyn FnOnce(Arg) -> Output>) -> Self {
let boxed_real_fn: Box<dyn Erased + '_> = Box::new(real_fn);
let boxed_real_fn: Box<dyn Erased + 'static> = unsafe {
// Step through *const T because *mut T is invariant over T
Box::from_raw(Box::into_raw(boxed_real_fn) as *const (dyn Erased + '_) as *mut (dyn Erased + 'static))
};
Self {
boxed_real_fn,
_phantom_fn: PhantomData,
}
}
pub fn call_once(self, arg: Arg) -> Output {
let boxed_real_fn: Box<Box<dyn FnOnce(Arg) -> Output>> = unsafe {
// Based on Box<dyn Any>::downcast()
Box::from_raw(Box::into_raw(self.boxed_real_fn) as *mut Box<dyn FnOnce(Arg) -> Output>)
};
boxed_real_fn(arg)
}
}
pub fn contravariant<'a, 'b: 'a>(x: VariantBoxedFnOnce<&'a (), ()>) -> VariantBoxedFnOnce<&'b (), ()> { x }
#[cfg(test)]
mod tests {
use super::*;
fn foo(_x: &()) {}
#[test]
pub fn check_fn_does_not_require_static() {
let f = VariantBoxedFnOnce::new(Box::new(foo));
let x = ();
f.call_once(&x);
}
#[test]
pub fn check_fn_arg_is_contravariant() {
let f = VariantBoxedFnOnce::new(Box::new(foo));
let g = contravariant(f);
let x = ();
g.call_once(&x);
}
}
Run Code Online (Sandbox Code Playgroud)
此处,VariantBoxedFnOnce仅限于采用一个参数的函数。
诀窍是存储消失的Box<dyn FnOnce(Arg) -> Output>的类型擦除版本Arg,因为我们不希望 的方差VariantBoxedFnOnce<Arg, Output>取决于Box<dyn FnOnce(Arg) -> Output>(它在 上是不变的Arg)。然而,还有一个PhantomData<fn(Arg) -> Output>字段可以提供适当的逆变Arg(和协方差Output)。
我们不能使用Any作为我们擦除的类型,因为只有'static类型实现Any,并且我们有一个步骤VariantBoxedFnOnce::new(),其中我们有一个Box<dyn Erased + '_>where'_不保证是'static。然后我们立即将其“转换”为'static,以避免在 上出现冗余的生命周期参数VariantBoxedFnOnce,但这'static是一个谎言(因此是unsafe代码)。call_once将擦除的类型“向下转换”为“原始” Box<dyn FnOnce(Arg) -> Output>,但Arg和Output可能由于差异而与原始类型不同。