Skip to content

rustc: co/contravariance markers for lifetimes seem backwards #15699

Closed
@aturon

Description

@aturon

The names for the CovariantLifetime and ContravariantLifetime markers seem backwards: they do not match the handling of lifetimes for functions, where arguments should be contravariant and results should be covariant.

Generally, a type T is a subtype of U if you can use a T anywhere a U is expected. For lifetimes, I would expect that 'a is a "subtype" of 'b if 'a is a larger scope than 'b. That would mean that 'static is a subtype of any lifetime -- so if I have a &'static T I can use it wherever &'a T is expected.

By that logic, if 'a is a larger lifetime than 'b and Foo is covariant, we should have Foo<'a> is a subtype of Foo<'b>, and the opposite if Foo is contravariant. These notions of variance line up correctly with Rust's closure types, which follow the typical rules:

  • The result position is covariant, so that |T| -> &'static U is usable wherever |T| -> &'a U is expected.
  • The argument position is contravariant, so that |&'a T| -> U is usable wherever |&'static T| -> U is expected.

But the CovariantLifetime and ContravariantLifetime markers seem to work backwards from everything described above. In particular, function arguments are treated as if they had the _co_variant marker, which is very surprising:

Update After discussion with @zwarich below, I think the real culprit here is that & takes its lifetime contravariantly, while it takes its type covariantly. I don't understand the rationale behind this choice. As I argue above, I think lifetime subtyping should be reverse region inclusion, which would mean & is _co_variant.

use std::kinds::marker::{CovariantLifetime, ContravariantLifetime};

// A straight-up reference:     'static => 'a
fn test_ref<'a>(t: &'static u8) -> &'a u8 {
    t
}

// Reference in a struct:       'static => 'a
struct Foo<'a> {
    t: &'a u8
}
fn test_struct<'a>(f: Foo<'static>) -> Foo<'a> {
    f
}

// fn with arg:                 'a => 'static
fn test_fn_in<'a, 'b>(f: |&'a u8|: 'b) -> |&'static u8|: 'b {
    f
}
// fn with result:              'static => 'a
fn test_fn_out<'a, 'b>(f: ||: 'b -> &'static u8) -> (||: 'b -> &'a u8) {
    f
}

// Covariant struct:            'a => 'static
struct FooCo<'a> {
    marker: CovariantLifetime<'a>
}
fn test_co<'a>(f: FooCo<'a>) -> FooCo<'static> {
    f
}

// Contravariant struct:        'static => 'a
struct FooContra<'a> {
    marker: ContravariantLifetime<'a>
}
fn test_contra<'a>(f: FooContra<'static>) -> FooContra<'a> {
    f
}

cc @nikomatsakis @pnkfelix @pcwalton

Nominating.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions