问题
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 atomsitem: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