Lifetimes are actually pretty simple, although anonymous lifetimes and the compiler inserting implicit lifetimes at compile-time can make the actual behaviour hard to parse.
But if you are saying you don't get it, and you are in the middle of a project (my state a week ago) then the issue is your code. References everywhere gets messy very quickly with Rust's borrow checking, and lifetimes should be used sparingly.
I will also give my very imprecise definition: adding a lifetime to a struct means those references in the struct cannot die before the struct dies itself (and where this can go wrong is having some kind of mutable state composed of references...because when you try to mutate that state from an implementation, the data will be destroyed as you go out of scope, and you have a dangling pointer).
They seem like an unnecessary complication, they certainly did to me when I was wading through compiler errors...then I try to do something wrong, and I realised why they exist. It is really worth sticking with.
Personally I learned how lifetimes work by implementing a zero-copy parser: it builds an AST that borrows from the original string instead of copying tokens out. This is one of those tasks where Rust really shines.
The parser was for a tiny language we use to express references within graphs over at membrane.io. similar to URLs but typed and not ambiguous. It's simple enough. Perhaps writing a URL parser would be good for learning.