For Cow<'a, B> the bound B: 'a seems unnecessary

From Cow's definition:

pub enum Cow<'a, B>
where
    B: 'a + ToOwned + ?Sized,
{
    Borrowed(&'a B),
    Owned(<B as ToOwned>::Owned),
}

So I have the following bounds: B: 'a + ToOwned + ?Sized.

B: ToOwned is necessary, e.g. to call Cow::to_mut.

B: ?Sized is a smart pointer convenience.

I don't quite see the necessity to require B: 'a explicitly. A reference is only valid as long as its referred-to value is valid, so &'a B "implies" B: 'a, does it not? The same should apply "transitively" even if B is a reference type, i.e. &'a &'b C implies C: 'b, 'b: 'a and C: 'a.

Somewhat connected to the above, the second example from the same documentation page also defines surprising bounds:

struct Items<'a, X> where [X]: ToOwned<Owned = Vec<X>> {
    values: Cow<'a, [X]>,
}

impl<'a, X: Clone + 'a> Items<'a, X> where [X]: ToOwned<Owned = Vec<X>> {
    fn new(v: Cow<'a, [X]>) -> Self {
        Items { values: v }
    }
}

Here X: Clone + 'a.

[X]: ToOwned<Owned = Vec<X>> already implies X: Clone, because that's the bound on impl<T> ToOwned for [T].

If X is an owned type, X: 'a is trivially implied.
If X was a reference, X: [X] is built into the language, and [X]: 'a is also necessary, as in the definition of Cow, to be able to store &'a B, where B is [X].

Is there then a reason to define both these bounds?

This is a case where the code was written to be more explicit than might be strictly necessary. The use of &'a T in the type definition does result in the T: 'a bound being implied, but writing it out is clearer to the user of the API.

X: [X] isn't a meaningful bound, nor present in your example.

In the struct Items example, I wouldn't write the X: Clone + 'a bound on the impl simply because the struct only uses the [X]: ToOwned bound, and matching the struct bound on the impl instead of using a different equivalent spelling is usually a good idea.

It's also possible that parts of this code was written before the current implied lifetime bounds rules and impl inference were in place, so more of the bounds could have been necessary. Cow has been around for a very long time.

2 Likes

Thank you for your reply.

I wrote X: [X] as part of X: [X] & [X]: 'a → X: 'a, only to show why I thought that bound was unnecessary.

Would there be a circumstance that warrants defining that bound, though, or is it always redundant, given [X]: ToOwned<Owned = Vec<X>> implying X: Clone and X: 'a as discussed above?

The bound wasn't added to be more clear.

That is part of the reason, but not the only reason. There is still a reason to add explicit bounds: they dictate the behavior of trait object lifetime elision.[1]

So here, where I've removed the lifetime bound, we can see that a Cow<'a, dyn Trait> in a function signature is really a Cow<'a, dyn Trait + 'static>. Whereas here, using the std Cow, it is a Cow<'a, dyn Trait + 'a>. Changing that would be a breaking change (due to invariance, but also because e.g. a return type that once required a trait lifetime of 'a would start requiring a trait lifetime of 'static[2]).

As shown above, it's not redundant when on a type parameter with a ?Sized bound, and that includes Cow<'a, B>.


  1. Or the RFC if you prefer. ↩︎

  2. i.e. changing default trait object lifetimes is breaking even when invariance isn't in play ↩︎

6 Likes

Oh, and I remembered another nuance which was decided post-RFC! 'static lifetime bounds aren't inferred because "it might be confusing" or so.[1]

// Fails to compile without an explicit `T: 'static` bound
struct MyRef<T>(&'static T);
struct MyRef<T: 'static>(&'static T);

// The exception applies even if the bound was explicit elsewhere,
// so this fails too
struct Nested<'a, T>(&'a mut MyRef<T>);

So that's another reason you may see explicit bounds on struct definitions (though unlike the default trait object lifetime consideration, the author does not get a choice in this case).


Note that this is a nuance of inferred bounds, not implied bounds, so once you add the T: 'static bound on the struct declaration when the compiler says you have to, it is still inferred elsewhere.

struct MyRef<T: 'static>(&'static T);

fn elsewhere<T>(_: MyRef<T>, t: T) {
    // Compiles thanks to the implied `T: 'static` bound
    let _: &'static mut T = Box::leak(Box::new(t));
}

As far as I'm aware this exception is not documented.


  1. Personally I find having an exception to the rule more confusing. It really threw me when trying to write my own lifetime guide. ↩︎

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.