2

I'm very intrigued by the error below. Somehow, the present of coefficients: Box<dyn M<'a, T, I> + 'a>, makes it impossible to borrow with as_my_iterator, but if you comment it out, it works. I truly have no idea why the mere presence of such member makes this error, but if you comment this line, you see that everything works.

use std::marker::PhantomData;

impl<'a, T, I> std::fmt::Debug for MyIterable<'a, T, I> {
    fn fmt(
        &self,
        f: &mut std::fmt::Formatter<'_>,
    ) -> std::fmt::Result {
        let x = self.as_my_iterator();
        Ok(())
    }
}

pub trait M<'r, T: 'r, I>
{
}

pub struct MyIterable<'a, T, I> {
    //Comment this line:
    coefficients:   Box<dyn M<'a, T, I> + 'a>,
    _phantom1: PhantomData<&'a T>,
    _phantom2: PhantomData<&'a I>
}

pub struct MyIterator<'a, T> {
    pub(crate) coefficients:   &'a [T],
}

pub trait AsMyIterator<'a, T> {
    fn as_my_iterator(&'a self) -> Result<MyIterator<'a, T>, ()>;
}

impl<'a, T, I> AsMyIterator<'a, T> for MyIterable<'a, T, I> {
    fn as_my_iterator(
        &'a self
    ) -> Result<
        MyIterator<'a, T>,
        (),
    > {
        todo!();
    }
}

https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=c3fc72de57ce1d941c81220dcab5cd71

Error:

error[E0495]: cannot infer an appropriate lifetime for autoref due to conflicting requirements
 --> src/lib.rs:8:16
  |
8 |         let x = self.as_my_iterator();
  |                      ^^^^^^^^^^^^^^
  |
note: first, the lifetime cannot outlive the anonymous lifetime defined here...

What is happening? And how could I solve it?

Rafaelo
  • 33
  • 1
  • 15
  • 1
    in my experience put a lifetime on a trait is **almost** always wrong => https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=919e8cf4b636b54794abdef6d9dbe38e – Stargateur Dec 13 '21 at 18:39
  • 3
    The presence of `dyn Trait<'a>` makes your type invariant over the lifetime `'a`, meaning the compiler is not allowed to change it in order to satisfy constraints. That and you're trying to use `self` as `&'a MyIterable<'a, ...>` which causes its own lifetime constraint [problems](https://stackoverflow.com/questions/66252831/why-does-this-mutable-borrow-live-beyond-its-scope) that are probably incompatible. I don't yet understand your code well enough to suggest a solution though. – kmdreko Dec 13 '21 at 19:40
  • @kmdreko I've just read about covariance and I understand the problem now. I was trying to create a trait for a MemoryPool, which is agonstic about the memory allocator being used. It can allocate short-lived slices. Full summary here: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=bb54cb268eb243ad4106ba66dd7f43c9. Somehow the allocated memory `Box` should be coerced into a smaller lifetime when I want to iterate over it. – Rafaelo Dec 13 '21 at 20:36

1 Answers1

1

This happens because of variance. Specifically, it's because &'a T (and consequently PhantomData<&'a T>) is covariant in 'a, but dyn M<'a, T, I> is invariant in 'a.

Variance is a subtle and rather abstract subject but here's a short, slightly wrong, mostly workable version: by using 'a in the trait object dyn M you have told the compiler that MyIterable<'a> may contain something analogous to a Cell<&'a T>. It is no longer safe to treat a MyIterable<'long> as if it were "merely" a MyIterable<'short>, because doing so might allow someone to call set and sneak a 'short reference into that hypothetical Cell which is supposed to only hold a 'long reference. The answers to this question include perhaps a more approachable long-form introduction to variance.

In your situation, invariance comes from introducing a trait object rather than a mutable reference. Traits (including trait objects and bounded type parameters) are always treated conservatively as invariant. Even though a trait might only be implemented by covariant types, there is no way to make a trait itself covariant, so if you write code that assumes covariance for M<'a>, anyone who came along and implemented M<'a> for a type not covariant in 'a could break your code by accident (or by malice).

What's interesting about variance is that it is one of the only things Rust will infer about your struct from its contents which other code can observe. Most of the time, adding a new field to a struct is a backwards-compatible change. But if the new field uses an existing type parameter with a different kind of variance, the variance of the whole struct will change, which can break code that was relying (possibly without your knowledge) on the old variance. The other things that are like variance in this respect are drop behavior and auto traits. These are also the reasons to use PhantomData in structs, and using PhantomData with an inappropriate type parameter can cause similar compatibility hazards.

Let's back up. When you are new to Rust, you may occasionally encounter very complicated problems not because you're doing something truly complicated, but because you've unintentionally complicated things for yourself by using lifetimes incorrectly. Lifetime parameters should be used sparingly and reused even less. Most of the time, you want every & to have a new, fresh lifetime, which allows the compiler maximal freedom to assign lifetimes. This is what lifetime elision does by default, so in a lot of Rust code you will not see any lifetimes at all. If you try to write lifetimes everywhere, and especially if you use the same lifetime several times in the same type (as in &'a self, where Self is a type involving 'a, and dyn M<'a, T, I> + 'a) you are very likely to find yourself at a dead end.

What to do

  1. Don't create a lifetime parameter unless you know you need one.
  2. When you do need a lifetime, make it distinct from all other lifetimes, unless you know it needs to be the same.

Here's one possibility:

impl<'a, 'r, T, I> std::fmt::Debug for MyIterable<'a, 'r, T, I> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let x = self.as_my_iterator();
        Ok(())
    }
}

// It may make sense to eliminate `'r` too, depending on what M means
pub trait M<'r, T: 'r, I> {}

pub struct MyIterable<'a, 'r, T, I> {
    coefficients: Box<dyn M<'r, T, I> + 'a>,
}

pub struct MyIterator<'a, T> {
    pub(crate) coefficients: &'a [T],
}

pub trait AsMyIterator<T> {
    fn as_my_iterator(&self) -> Result<MyIterator<'_, T>, ()>;
}

impl<'a, 'r, T, I> AsMyIterator<T> for MyIterable<'a, 'r, T, I> {
    fn as_my_iterator(&self) -> Result<MyIterator<'_, T>, ()> {
        todo!();
    }
}

See also

trent
  • 25,033
  • 7
  • 51
  • 90