在无意中发现下面的代码之前,我确信类型的life参数中的life总是比它自己的实例生命周期 长.换句话说,如果给出foo: Foo<'a>,那么'a将永远比foo长寿.然后@Luc Danton(Playground)向我介绍了这个反参数代码:

#[derive(Debug)]
struct Foo<'a>(std::marker::PhantomData<fn(&'a ())>);

fn hint<'a, Arg>(_: &'a Arg) -> Foo<'a> {
    Foo(std::marker::PhantomData)
}

fn check<'a>(_: &Foo<'a>, _: &'a ()) {}

fn main() {
    let outlived = ();
    let foo;

    {
        let shortlived = ();
        foo = hint(&shortlived);
        // error: `shortlived` does not live long enough
        //check(&foo, &shortlived);
    }

    check(&foo, &outlived);
}

尽管由hint创建的foo似乎考虑了一个生命周期,该生命周期不会像它自己那样长,而它的引用被传递到一个更广泛的范围内的函数,该代码按照原样进行编译.取消对代码中所述行的注释会触发编译错误.或者,将Foo更改为 struct 元组(PhantomData<&'a ()>)也会使代码不再以同样的错误进行编译(Playground).

它是如何有效的防 rust 代码?编译器的推理是什么?

推荐答案

尽管你的初衷是好的,但你的hint功能可能没有达到你预期的效果.但在我们了解发生了什么之前,我们还有很多事情要做.


让我们从这个开始:

fn ensure_equal<'z>(a: &'z (), b: &'z ()) {}

fn main() {
    let a = ();
    let b = ();
    ensure_equal(&a, &b);
}

main中,我们定义了两个变量,ab.它们有不同的生命周期,因为它们由不同的let条语句引入.ensure_equal需要两个参考文献和the same lifetime.然而,这段代码是可以编译的.为什么?

这是因为,给定'a: 'b('a'b长寿),&'a T&'b T中的subtype.

假设a的生命周期 是'ab的生命周期 是'b.'a: 'b是事实,因为a是首先引入的.调用ensure_equal时,参数分别键入&'a ()&'b ()1.这里有一个类型不匹配,因为'a'b不是同一个生命周期.但是编译器还没有放弃!它知道&'a ()&'b ()的一个亚型.换句话说,一个&'a () is a &'b ().因此,编译器将强制表达式&a的类型为&'b (),这样两个参数的类型都为&'b ().这解决了类型不匹配的问题.

如果你对"子类型"的应用和生命周期感到困惑,那么让我用Java术语重新表述这个例子.让我们用Programmer替换&'a (),用Person替换&'b ().现在让我们假设Programmer源于Person:ProgrammerPerson的一个亚型.这意味着我们可以获取Programmer类型的变量,并将其作为参数传递给需要Person类型参数的函数.这就是为什么下面的代码将成功编译:对于main中的调用,编译器将T解析为Person.

class Person {}
class Programmer extends Person {}

class Main {
    private static <T> void ensureSameType(T a, T b) {}

    public static void main(String[] args) {
        Programmer a = null;
        Person b = null;
        ensureSameType(a, b);
    }
}

也许这种子类型关系的非直观方面是,较长的生命周期 是较短生命周期 的一个子类型.但可以这样想:在Java中,假装ProgrammerPerson是安全的,但不能假设PersonProgrammer.同样,可以假设一个变量有shorter个生命周期,但不能假设一个已知生命周期的变量实际上有longer个生命周期.毕竟,Rust的整个使用生命周期 是为了确保在实际使用生命周期 之外不会接触到对象.


现在,让我们谈谈variance.那是什么?

方差是类型构造函数对其参数具有的属性.Rust中的类型构造函数是具有未绑定参数的泛型类型.例如,Vec是一个类型构造函数,它接受T并返回Vec<T>.&&mut是接受两个输入的类型构造函数:生存期和指向的类型.

通常情况下,你会期望Vec<T>的所有元素都具有相同的类型(这里我们不是在讨论trait对象).但变异让我们可以用它作弊.

&'a T等于covariant除以'aT.这意味着,只要我们在类型参数中看到&'a T,我们就可以用&'a T的子类型替换它.让我们看看结果如何:

fn main() {
    let a = ();
    let b = ();
    let v = vec![&a, &b];
}

我们已经确定ab有不同的生存期,表达式&a&b没有相同的类型1.那么为什么我们能用这些做一个Vec呢?推理与上面相同,因此我将总结:&a被强制为&'b (),因此v的类型是Vec<&'b ()>.


fn(T)是Rust 的特例.fn(T)contravariantT.让我们构建一个Vec个函数!

fn foo(_: &'static ()) {}
fn bar<'a>(_: &'a ()) {}

fn quux<'a>() {
    let v = vec![
        foo as fn(&'static ()),
        bar as fn(&'a ()),
    ];
}

fn main() {
    quux();
}

这是一本汇编.但是quux中的v是什么类型呢?是Vec<fn(&'static ())>还是Vec<fn(&'a ())>

我给你一个提示:

fn foo(_: &'static ()) {}
fn bar<'a>(_: &'a ()) {}

fn quux<'a>(a: &'a ()) {
    let v = vec![
        foo as fn(&'static ()),
        bar as fn(&'a ()),
    ];
    v[0](a);
}

fn main() {
    quux(&());
}

这是doesn't美元.以下是编译器消息:

error[E0495]: cannot infer an appropriate lifetime due to conflicting requirements
 --> <anon>:5:13
  |
5 |       let v = vec![
  |  _____________^ starting here...
6 | |         foo as fn(&'static ()),
7 | |         bar as fn(&'a ()),
8 | |     ];
  | |_____^ ...ending here
  |
note: first, the lifetime cannot outlive the lifetime 'a as defined on the body at 4:23...
 --> <anon>:4:24
  |
4 |   fn quux<'a>(a: &'a ()) {
  |  ________________________^ starting here...
5 | |     let v = vec![
6 | |         foo as fn(&'static ()),
7 | |         bar as fn(&'a ()),
8 | |     ];
9 | |     v[0](a);
10| | }
  | |_^ ...ending here
note: ...so that reference does not outlive borrowed content
 --> <anon>:9:10
  |
9 |     v[0](a);
  |          ^
  = note: but, the lifetime must be valid for the static lifetime...
note: ...so that types are compatible (expected fn(&()), found fn(&'static ()))
 --> <anon>:5:13
  |
5 |       let v = vec![
  |  _____________^ starting here...
6 | |         foo as fn(&'static ()),
7 | |         bar as fn(&'a ()),
8 | |     ];
  | |_____^ ...ending here
  = note: this error originates in a macro outside of the current crate

error: aborting due to previous error

我们试图用&'a ()参数调用向量中的一个函数.但是v[0]需要&'static (),并且不能保证'a'static,所以这是无效的.因此,我们可以得出结论,v的类型是Vec<fn(&'static ())>.正如你所看到的,反方差与协方差相反:我们可以用longer年的生命周期 来代替短暂的生命周期 .


喂,现在回到你的问题上来.首先,让我们看看编译器如何利用对hint的调用.hint有以下签名:

fn hint<'a, Arg>(_: &'a Arg) -> Foo<'a>

Foo代表contravariant'a,因为Foo代表fn(或者更确切地说,pretends to,多亏了PhantomData,但当我们谈论方差时,这没有什么区别;两者都有相同的效果),fn(T)代表T的逆变,这里的T代表&'a ().

当编译器试图将调用解析为hint时,它只考虑shortlived的生存期.因此,hint返回Fooshortlived的生命周期 .但当我们试图将其分配给变量foo时,我们遇到了一个问题:类型上的生存期参数总是比类型本身的生存期长,而shortlived的生存期并不比foo的生存期长,所以很明显,我们不能将该类型用于foo.如果Foo的协变系数大于'a,那么就结束了,你会得到一个错误.但是Foocontravariant除以'a,所以我们可以用larger代替shortlived的生命周期 .这一生可以是任何超过foo岁的一生.请注意,"超过生命周期 "与"严格超过生命周期 "不同:区别在于'a: 'a('a超过'a)是真的,但'a严格超过'a是假的(也就是说,一个人的一生据说比自己活得长,但它本身并不是strictly outlive).因此,我们可能最终得到foo具有Foo<'a>型,其中'a正好是foo本身的生命周期 .

现在让我们看看check(&foo, &outlived);(这是第二个).这一个编译是因为&outlived被强制,所以生命周期被缩短,以匹配foo的生命周期.这是有效的,因为outlived的生命周期 比foo长,而check的第二个参数是'a的协变参数,因为它是一个引用.

为什么check(&foo, &shortlived);不能编译?foo的生命周期 比&shortlived长.check的第二个参数是'a的协变,但它的第一个参数是contravariant'a,因为Foo<'a>是逆变的.也就是说,这两个论点都试图将'a拉向这个调用的相反方向:&foo试图扩大&shortlived的生命周期 (这是非法的),而&shortlived试图缩短&foo的生命周期 (这也是非法的).没有将这两个变量统一起来的生存期,因此调用无效.


1 That might actually be a simplification. I believe that the lifetime parameter of a reference actually represents the region in which the borrow is active, rather than the lifetime of the reference. In this example, both borrows would be active for the statement that contains the call to 100, so they would have the same type. But if you split the borrows to separate 101 statements, the code still works, so the explanation is still valid. That said, for a borrow to be valid, the referent must outlive the borrow's region, so when I'm thinking of lifetime parameters, I only care about the referent's lifetime and I consider borrows separately.

Rust相关问答推荐

SQL x中的mut *transaction和mut transaction有什么区别?

如何定义使用拥有的字符串并返回拥有的Split的Rust函数?

为什么我的梅森素数代码的指数越大,速度就越快?

如何在Rust中实现Functor trait?

使用Clap时如何将String作为Into Str参数传递?

制作一片连续整数的惯用Rust 方法?

在Rust中有没有办法在没有UB的情况下在指针和U64之间进行转换?

对于已经被认为是未定义行为的相同数据,纯粹存在`&;[u32]`和`&;mut[u32]`吗?

将Vec<;U8&>转换为Vec<;{Float}&>

如何实现Deref;多次;?

如何强制匹配的返回类型为()?

Rust 如何返回大类型(优化前)?

在 Rust 中查找向量中 dyn struct 的索引

‘&T as *const T as *mut T’ 在 ‘static mut’ 项目中合适吗?

(let b = MyBox(5 as *const u8); &b; ) 和 (let b = &MyBox(5 as *const u8); ) 之间有什么区别

具有在宏扩展中指定的生命周期的枚举变体数据类型

在 Rust 中,我如何处理请求 javascript 的页面?

Rust 中的方法调用有什么区别?

如何在 Rust 中将 bson::Bson 转换为 Vec

Rust 为什么 (u32, u32) 的枚举变体的大小小于 (u64)?