Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Covariance combined with contravariance is not invariance unless observed. #31200

Closed
eddyb opened this issue Jan 26, 2016 · 5 comments
Closed
Labels
A-lifetimes Area: lifetime related

Comments

@eddyb
Copy link
Member

eddyb commented Jan 26, 2016

Id<'a> does not appear to be invariant over 'a if the whole type is inferred (run on playpen):

type Id<'a> = fn(&'a ()) -> &'a ();
fn id<T>(x: T) -> T {x}
fn scope<F: for<'a> FnOnce(Id<'a>)>(f: F) { f(id) }
fn same<T>(_: T, _: T) {}

fn main() {
    // remove ::<Id> to hide error
    scope(|a| scope(|b| same::<Id>(a, b)));

    scope(|a| scope(|b| {
        let mut k: Id = a; // remove : Id to hide error
        k = b;
    }));
}

This testcase was derived from a (misbehaving) invariance check using fn(T) -> T by @bluss.

I'm not sure this is a soundness issue, because the regionck error appears as soon as the lifetime parameter is instantiated, even from trait impls, so only code completely generic over the type compiles.

However, specialization would allow observing Id<'a> through a completely generic function (like same) without explicit bounds, so it might be interesting to see what happens on #30652.

cc @nikomatsakis @arielb1 @aturon

@eddyb eddyb added the A-lifetimes Area: lifetime related label Jan 26, 2016
@arielb1
Copy link
Contributor

arielb1 commented Jan 26, 2016

This is just plain silly. If you don't provide type inference hints, same is inferred to same::<fn(&'static ()) -> &'short ()>, and both a and b are supertypes of that.

Of course, if you supply a type inference hint both lifetimes are forced to be the same - this also happens if you wrap Id in a struct.

@arielb1 arielb1 closed this as completed Jan 26, 2016
@pnkfelix
Copy link
Member

Just to clear things up for future readers (because I had to re-read the two messages here a couple times before I understood @arielb1's diagnosis of what was "silly" here): The first line of the issue description:

Id<'a> does not appear to be invariant over 'a if the whole type is inferred

is not a correct conclusion to draw from the code presented, because removing the type annotations (i.e. same::<Id> ==> same and let mut k: Id ==> let mut k) does not cause the type inference system to infer the type Id<'a>; it causes it to infer an entirely different type (that @arielb1 provided) with two distinct lifetimes that can be independently varied.


Update: just to complete my elaboration of the points being made above, here is a playpen illustrating what @arielb1 was saying about wrapping Id in a struct instead of supplying a type-annotation: http://is.gd/tmBCKr

@eddyb
Copy link
Member Author

eddyb commented Jan 26, 2016

Thanks for clearing this up, I can see it now. I should've gotten rid of Id from @bluss' testcase and then maybe it would've been obvious.

@bluss
Copy link
Member

bluss commented Jan 26, 2016

This is a curiosity from a discussion about different ways to write an Invariant type parameter marker; since we need to express it via PhantomData.

candidates were basically (although this was only used for &'a () cases)

  1. PhantomData<*mut T>
  2. PhantomData<Cell<T>>
  3. PhantomData<fn(T) -> T>

Which all have different drawbacks and incidental other implications.

The source of the confusion was (3) case not passing my simple test for invariance.

Because of the drawbacks, it seems it would be useful to have a real invariance marker in rust's libstd. @reem pointed out some of these

(1) and (2) make the type !Sync. (2) has owns-T implications (3) has this bug. All of (1), (2), (3) don't respond like Invarant<T> should to custom new OIBITS.

@eddyb
Copy link
Member Author

eddyb commented Jan 26, 2016

@bluss This is not a bug, you have fn(T) -> U, not fn(T) -> T, unless the type alias is applied.
For a phantom marker in a struct, your type alias works fine.
But you were testing that the property survives being passed around by value, which resulted in fn(T) -> U appearing.
struct Invariant<T>(PhantomData<fn(T) -> T>); will work in all contexts, if you're really worried about all that, although your tests aren't the common usecase.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-lifetimes Area: lifetime related
Projects
None yet
Development

No branches or pull requests

4 participants