1

Here I have a working example. I take a new stdin handle in main. I pass it to the function parse_something. In this function stdin.lock() returns a lockguard which I store into a struct that is finally returned. Picture the struct being some sort of iterator. And I would like this iterator to keep stdin locked as long as it is live. So far so good. The lockguard is linked to the lifetime 'a. Which started at the new stdin handle in main.

use std::io::BufRead;

struct SomeIterator<B: BufRead> {
    buffer: B,
}

fn parse_something<'a>(stdin: &'a std::io::Stdin) -> SomeIterator<std::io::StdinLock<'a>> {
    let r = SomeIterator {
        buffer: stdin.lock(),
    };
    r
}

fn main() {
    let stdin = std::io::stdin();
    parse_something(&stdin);
}

Let's now try to move the stdin handle instantiation inside the parse_something function.

use std::io::BufRead;

struct SomeIterator<B: BufRead> {
    buffer: B,
}

fn parse_something<'a>() -> SomeIterator<std::io::StdinLock<'a>> {
    let stdin = std::io::stdin();
    let r = SomeIterator {
        buffer: stdin.lock(),
    };
    r
}

fn main() {
    parse_something();
}

I get the error error[E0597]: `stdin` does not live long enough. So far so good. stdin must live as long as 'a. And for that, I should probably store the stdin handler inside SomeIterator.

The catch is I also must take a .lock() on the stdin handle with the same lifetime 'a. I cannot figure out how.

This doesn't work because the lockguard references the let stdin variable, not on the (eventually moved) stdin attribute within SomeIterator.

struct SomeIterator<B: BufRead> {
    stdin: std::io::Stdin,
    buffer: B,
}

fn parse_something<'a>() -> SomeIterator<std::io::StdinLock<'a>> {
    let stdin = std::io::stdin();
    let guard = stdin.lock();
    let r = SomeIterator {
        stdin: stdin,
        buffer: guard,
    };
    r
}

Eventually, I went all in, and added some random crap in the hope that perhaps I get lucky and stumble on the solution. No luck though. Here, I put stdin in a box. Technically, the content of the box should never be moving around in memory. Only the box "pointer object" is. I would hope that r.stdin.lock() actually uses the lifetime of the content within the Box. Which should be 'a since the Box is part of SomeIterator which itself is generic over 'a.

Sadly everything goes to shit when r.stdin.lock() actually borrows r.stdin before calling .lock(). This borrow only last for the scope of the function. `*r.stdin` does not live long enough hits hard.

struct SomeIterator<'a, B: BufRead + 'a> {
    stdin: Box<std::io::Stdin>,
    buffer: Option<B>,
    _marker: std::marker::PhantomData<&'a B>,
}

fn parse_something<'a>() -> SomeIterator<'a, std::io::StdinLock<'a>> {
    let mut r = SomeIterator {
        stdin: Box::new(std::io::stdin()),
        buffer: None,
        _marker: std::marker::PhantomData{},
    };
    r.buffer = Some(r.stdin.lock());
    r
}

I suspect that after weeding out the details, my question ultimately becomes: is it possible in safe Rust to store a reference to a sibling attribute? But I could be wrong.

bombela
  • 613
  • 4
  • 8

0 Answers0