Problem with generic traits and lifetimes

巧了我就是萌 提交于 2021-01-28 21:55:36

问题


I had a problem with generic traits in a larger context and try to size it down to this smaller problem. I want to have following function:

fn count<I, S, T>(pattern: T, item:I) -> usize
where
  I: Atom,
  S: Iterator<Item = I> 
  T: Atoms<I, S>,
{
  pattern.atoms().filter(|i| i == &item).count()
}

The function should be passed two arguments:

  • pattern:Atoms<...> which has a factory method atoms returning an iterator of atoms
  • item:Atom which is the item that should be counted in the iterator of atoms

The function should be generic over both arguments (with the constraint that pattern.atoms() must return an iterator with items of the same type as item), e.g.:

count(Atoms<u8, std::str::Bytes<'_>>, u8) -> usize
count(Atoms<char, std::str::Chars<'_>>, char) -> usize
count(Atoms<u8, std::io::Bytes>, u8) -> usize

For Atom I tried this approach:

pub trait Atom: Copy + Eq + Ord + Display + Debug {}
impl Atom for char {}
impl Atom for u8 {}

Next I started with Atoms, first without lifetimes:

pub trait Atoms<I, S>
where
  S: Iterator<Item = I>,
  I: Atom,
{
  fn atoms(&self) -> S;
}

I tried to implement this for str using std::str::Bytes<'a> as iterator. Consequently the compiler misses a lifetime annotation &'a self in atoms. So I refined this approach with lifetimes and came up with this code:

pub trait Atoms<'a, I, S>
where
  S: Iterator<Item = I> + 'a,
  I: Atom,
{
  fn atoms(&'a self) -> S;
}

impl<'a> Atoms<'a, u8, std::str::Bytes<'a>> for &str {
  fn atoms(&'a self) -> std::str::Bytes<'a> {
    self.bytes()
  }
}

fn count<'a, I, S, T>(pattern: T, item: I) -> usize
where
  I: Atom,
  S: Iterator<Item = I> + 'a,
  T: Atoms<'a, I, S>,
{
  pattern.atoms().filter(|i| *i == item).count() //<--- compiler complains 
}

Playground-Link

Now the compiler complains the parameter type T may not live long enough, ... consider adding ... T:'a. I do not understand this problem. The hint does not even work (another lifetime problem follows) so I have no clue what my misunderstanding is. Can someone help me to understand (or even fix) this problem?


回答1:


The atoms function requires self to be borrowed for a lifetime of 'a. If you give to this function a type that does not live that long, this function cannot work properly.

For example, the type &'b T has a lifetime of 'b. If you implement Atoms for &'b T and that 'b is shorter that 'a, you must not be able to call the atoms function. This is why you must constrain T to live at least as long as 'a.

Rust knows this and automatically do this to the atoms function:

fn atoms(&'a self) -> std::str::Chars<'a>
where
    Self: 'a,
{ self.chars() }

But when you try to use that function inside of another function, Rust needs you to add this bond yourself. This is why you need to add it for the count function.

The second problem

Your Atom trait does not need to carry the 'a lifetime. Your first approach was the right one.

pub trait Atoms<S>
where
  S: Iterator,
  S::Item: Atom,
{
  fn atoms(&self) -> S;
}

(note that I removed the generic parameter I and replaced it with S::Item. This is possible because Item is an associated type of Iterator. That changes nothing apart from being clearer.)

After that, the implementations are pretty straightforward.

// Note the `&'a str` here, this was missing on your implementation but it is actually
// needed. If you think about it, the bytes that are returned are part of the `&str`
// so they must live at least as long as the reference.
impl<'a> Atoms<std::str::Bytes<'a>> for &'a str {
    // The difference is here. You don't need to chose a lifetime for `&self`.
    // A longer lifetime than `'a` would be ok. That would be a reference to
    // a reference of a lifetime of `'a`.
    //
    // `self` is `&'a str` here.
    // so `&self` is `&&'a str`
    //
    // What you did was telling Rust that that second reference needed to live
    // as long as `'a`. Meaning `&'a &'a str`. But this was wrong. That reference
    // must be able to live longer than `'a`.
    fn atoms(&self) -> std::str::Bytes<'a> {
        self.bytes()
    }
}

After that, the count function work just as intended:

fn count<S, T>(pattern: T, item: I) -> usize
where
  S::Item: Atom,
  S: Iterator,
  T: Atoms<S>,
{
  pattern.atoms().filter(|i| *i == item).count() //<--- compiler does not complain anymore ;)
}


来源:https://stackoverflow.com/questions/65618771/problem-with-generic-traits-and-lifetimes

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!