So I stumbled upon this snippet where a manager (struct Library
) manages something (struct Book
) and lend them out (impl trait Reader
).
Library
itself records who had requested such services, say with a Vec<&mut impl Reader>
.
trait Reader
has methods that struct Library
uses to determine if to lend books, and implementations probably keep some &Book
in it, say Vec<&book>
.
Here's 4 possible configurations and the same usage snippet. You might want to check out playground with nightly #[cfg(true)]
/#[cfg(false)]
toggles.
//! prob_a0
pub enum Proxy<'t, T> {
None,
Some(&'t mut T),
}
pub struct Library<'reader, R> {
readers: Vec<Proxy<'reader, R>>,
}
impl<'reader, R: Reader<'reader> + 'reader> Library<'reader, R> {
pub fn new(readers: impl IntoIterator<Item = &'reader mut R>) -> Self {
Self {
readers: Vec::from_iter(readers.into_iter().map(Proxy::Some)),
}
}
}
//! prob_a1
pub enum Proxy<'t, T> {
None,
Some(&'t mut T),
}
pub struct Library<'reader, R> {
readers: Vec<Proxy<'reader, R>>,
}
impl<'book, 'reader, R: Reader<'book> + 'book> Library<'reader, R>
where
'book: 'reader,
{
pub fn new(readers: impl IntoIterator<Item = &'reader mut R>) -> Self {
Self {
readers: Vec::from_iter(readers.into_iter().map(Proxy::Some)),
}
}
}
//! prob_b0
pub struct Library<'reader, R>
where
R: Reader<'reader>,
{
readers: Vec<&'reader mut R>,
}
impl<'reader, R: Reader<'reader> + 'reader> Library<'reader, R> {
pub fn new(readers: impl IntoIterator<Item = &'reader mut R>) -> Self {
Self {
readers: Vec::from_iter(readers),
}
}
}
//! prob_b1
pub struct Library<'reader, R>
where
R: Reader<'reader>,
{
readers: Vec<&'reader mut R>,
}
impl<'book, 'reader, R: Reader<'book> + 'book> Library<'reader, R>
where
'book: 'reader,
{
pub fn new(readers: impl IntoIterator<Item = &'reader mut R>) -> Self {
Self {
readers: Vec::from_iter(readers),
}
}
}
// all the problems may use this same snippet for testing
use std::iter::repeat_n;
#[derive(Debug)]
struct Book;
trait Reader<'reader> {
fn borrow(&mut self, book: &'reader Book);
}
#[derive(Default, Debug)]
struct GenericReader<'reader> {
books: Option<&'reader Book>,
}
impl<'reader> Reader<'reader> for GenericReader<'reader> {
fn borrow(&mut self, book: &'reader Book) {
self.books.replace(book);
}
}
fn lifetime() {
let books = vec![Book, Book, Book];
let mut readers = Vec::from_iter(repeat_n((), 5).map(|_| GenericReader::default()));
{
let library = Library::new(&mut readers);
}
dbg!(&readers);
}
And they are basically the four configurations of
- Whether to add another generic lifetime parameter to the
impl Library
(0
vs1
) - Whether if wrap the
impl Reader
with someenum
(a
vsb
)
So the main question are these three:
- Why. Just, why.
let library = Library::new( /* snip */ )
may once contained some exclusive references to someimpl Reader
, but thelibrary
is gone at the end of the local scope. No one is borrowing nothing at thedbg!
. But 3 out of 4 configurations fails to compile. This just defies my generic PoV on Rust code. - On the surface the
prob_a1
is allowing the inputimpl Reader
to have a different lifetime (larger) compared to that inprob_a0
. And we knowprob_a1
compiles whileprob_a0
doesn't.- How does it work? How come there exists some pair of lifetimes satisfying
prob_a1
but not a single standalone lifetime is possible for theprob_a0
configuration? - Is it related to the fact functions being contravariant, and I suppose Rust checks lifetime validity of all trait methods?
- How does variance come into play exactly?
- How does it work? How come there exists some pair of lifetimes satisfying
prob_a1
andprob_b1
are similar, both trying to work around the lifetime bound, presumably by somehow relaxing the lifetime constraints of some sort by adding another generic lifetime parameter. But apparentlyprob_b1
fails to compile, while semantically it's (IMHO) basically the same asprob_a1
: they both grant access to the&mut impl Reader
if we have&mut Library
. What's the deal here?
I'm so utterly confused. Please help shed some light.