Although the "LendingIterator" example is apparently done to death, I'm going to try to explain it in a way that is actually appropriate for "ELI5".
In Rust, there are many different ways to have a list of things that come one after another. But sometimes we want to write some code that doesn't care about the details like whether the list is a list of words, or a list of numbers, it just cares about the fact that there are a list of things in order.
Right now, we call these general lists of things in order "Iterators", with code like this:
trait Iterator {
type Item;
fn next(&mut self) -> Self::Item;
}
This tells us what an "Iterator" means. "Item" is the type of thing that is in the list, for example it could be "number" or "word". "next" is a function, which means it is something we can do. When we do the "next" function, we get the next thing in the list. So if we keep doing the "next" function over and over, we can get everything in the Iterator one by one.
However, sometimes we want to have more complicated lists, where the "next" function doesn't actually get us the next thing in the list, it just tells us the address of the next thing inside the computer. We would write this code like this:
trait StaticIterator {
type Item;
fn next(&mut self) -> &'static Self::Item;
}
However, this requires the thing in the list to
always be at the address (that's what "&'static" means). But a lot of the time the thing stays at the address while we are looking at the list, and later it will go away.
We could write code saying that the things only have to be at the address for a certain period of time. It would look like this:
trait ReferenceIterator {
type Item;
fn next(&mut self) -> &'a Self::Item;
}
This solves that problem, because now we are saying the address only has to have the thing at it for the the time period "a" (we call "a" the lifetime, because it tells us how long the things will live at the address). The problem is, that when we write "Iterator" we have to decide
up front what the lifetime will be. So we can't use the same code for lists with different lifetimes, we would need to write the same code twice, once for each lifetime.
This problem is what GATs help us with. GATs are a way to say that we are going to have a list of addresses, where the lifetimes could be different for each list. We would write the code like this:
trait LendingIterator {
type Item where Self: 'a;
fn next(&mut self) -> Self::Item;
}
This allows us to write code which doesn't decide up front what the lifetime of the addresses will be. Instead, the same code can happily work with lists of addresses with
any lifetime. GATs may seem scary at first, but as you can see, they are not doing anything fancy, just allowing us to do what we
should be able to do: write one piece of code that can work with lists of addresses with different lifetimes.