Where does the document say that the satisfaction relationship is transitive between two trait bounds?

Consider this example:

fn call<'a:'static, F>(f: F)
where
    F: Fn(&'a str),
{
    call2(f);
}

fn call2<F>(_: F)
where
    F: Fn(&'static str),
{
}

fn main() {}

This code can be compiled. The result seems to imply that if F satisfies Fn(&'a T), it will also satisfy Fn(&'b T) if 'a:'b. This seems to not only apply to Fn Trait but also for customized traits, for example:

trait MyTrait<'a> {}


fn call<'a:'static, F>(f: F)
where
    F: MyTrait<'a>,
{
    call2(f);
}

fn call2<F>(_: F)
where
    F: MyTrait<'static>,
{
}

fn main() {}

I don't find any document that tells this point.

Yes, this is a consequence of something called variance.
'a: 'b implies that 'a is a subtype of 'b, ie, 'a can be used wherever 'b can be used.
Now, according to the variance docs, &'a T is covariant over 'a. This means that if 'a: 'b, &'a T can be used in place of &'b T. Hence, your observation holds.

However, the trait bound is not type, the variance only applies to type.

1 Like

No, this conclusion is wrong. Your code works, because 'a: 'static implies that 'a is exactly the 'static lifetime – there is nothing else that outlives 'static but for 'static itself :wink:


Here’s the more general case, where it does break:

fn call<'b, 'a: 'b, F>(f: F)
where
    F: Fn(&'a str),
{
    call2::<'b>(f);
}

fn call2<'b, F>(_: F)
where
    F: Fn(&'b str),
{
}
1 Like

How about this one?

fn call<'b, F>(f: F)
where
    F: for<'a> Fn(&'a str),
{
    let s: &'b str = "a";
    f(s);
    call2(f);
}

fn call2<F>(_: F)
where
    F: Fn(&'static str),
{
}

This can be compiled too.

Sure, it can! F with such a higher-rank trait bound (HRTB) [(1), (2)] supports arguments of all lifetimes, so it can be passed an argument with any lifetime, and serve as a Fn-implementor for any concrete lifetime. Is there anything confusing you about this? Feel free to ask more concrete questions, or more concretely share your thoughts on why that code example might not compile :wink:

From the variance perspective, the function pointer/trait object with a higher-ranked lifetime is a subtype of the one with a specific lifetime. However, this variance does not apply to the trait bound for checking satisfaction, as shown in your above example. Your example exposes that the satisfaction of trait bound is not transitive, however, it does work between the higher-ranked trait bound and common trait bound. I didn't find the relevant document that clarifies this point.

Is the higher-ranked trait bound the unique case that can make the satisfaction of constraints transitive?

Your example doesn’t contain any function pointer not trait objects; so we’re skipping to another (related but different) topic/aspect again?

Function pointer types or trait object types related via "more general" vs "more specific" are indeed in a subtyping relationship.

This subtyping relation between more general and more specific function pointers or trait objects is not really related much to variance because variance describes how different subtyping relationships transfer, for example from something like T :<: U to S<T> :<: S<U> (if we wrote subtyping with “:<:”, and if S is a type constructor that’s covariant in its argument), whereas here we’re establishing a primitive/initial kind of subtyping relation not derived from other ones.

Subtyping is a relation between types, so it can’t “apply” or “not apply” to trait bounds; I wouldn’t even know what exactly you mean by that.

I can acknowledge that the trait system is not thoroughly documented, as far as I’m aware. The fact that T: for<'a> Trait<'a> also gives you T: Trait<'b> for any “concrete”[1] is however kind-of the very definition of what for<…> …, i.e. “higher-rank trait bounds” even mean in the first place. Or as the nomicon (linked in my previous reply puts it):

for<'a> can be read as "for all choices of 'a ", and basically produces an infinite list of trait bounds that F must satisfy. Intense.

I don’t really understand what you mean by “transitive” when talking about trait bounds here. I suppose perhaps, you mean a lack of a rule like T: Trait<'foo> and 'foo: 'bar implies T: Trait<'bar>? Note that isn’t really looking like transitivity though[2] (to me, at least; and ignoring the fact that the same : being used here does have somewhat significantly different meaning, the difference between a “type -implements- trait” and a “lifetime -outlives- lifetime” constraint) because you have the lifetime ('foo) wrapped in Trait on one side, and on its own on the other side.

Typical other examples where T: SomeTrait implies T: OtherTrait, for a generic type parameter T, besides the case here where (in place of) SomeTrait you have a HRTB that’s more general than OtherTrait, include e.g.:

  • when OtherTrait is a supertrait of SomeTrait
  • when there exists a generic impl<T> OtherTrait for T where T: SomeTrait
  • if we’re talking about whole trait bound expressions, then of course also something like T: Trait1 + Trait2 will imply T: Trait1 or T: Trait2 itself
    • there are also many relations of the form T: SomeTrait implying OtherType<T>: OtherTrait, or multiple constraints together implying a different, new one…

  1. well, as “concrete” as lifetimes get… so 'static, or another parameter from an outer context… ↩︎

  2. transitivity of a binary relation ⊰ would usually look like x ⊰ y and y ⊰ z implies x ⊰ z ↩︎

1 Like

Ok, you supplied other cases here. What I meant in my question is that the case where two trait bounds only differ in lifetimes. For example, T: Trait<'a> and T:Trait<'b>, if T:Trait<'a> then T:Trait<'b>, this seems to only apply to higher-ranked lifetime[1]?


  1. Because your last example in this comment seems to embody this point ↩︎