Why doesn't `io::stdin().lock().lines().count()` work?


#1

I would like to understand in full technical detail why this doesn’t work.

Note that I’m not asking how to fix this.

use std::io;
use std::io::prelude::*;

fn main() {
    let n = io::stdin().lock().lines().count();
    println!("{}", n);
}

The error message is:

rustc 1.17.0-nightly (ccce2c6eb 2017-03-27)
error: borrowed value does not live long enough
 --> <anon>:5:47
  |
5 |     let n = io::stdin().lock().lines().count();
  |             -----------                       ^ temporary value dropped here while still borrowed
  |             |
  |             temporary value created here
  |
  = note: values in a scope are dropped in the opposite order they are created
  = note: consider using a `let` binding to increase its lifetime

I expect temporary values also to be dropped in the opposite order they are created. In which case io::stdin() would in fact live to the end of this expression, which ought to be long enough.


Borrowed reference to a temporary
#2

Rust has this weird thing, where:

foo().bar()

is not equivalent to:

let x = foo();
x.bar();

and this is one of them.

io::stdin() creates a new object, and the compiler doesn’t know how long this object should live — is it temporary only for the expression, or should it live for the entire function? Rust assumes it is temporary only for the expression, but it’s not true in your case, since the result is assigned to n and has to live as long as this variable.

use std::io;
use std::io::prelude::*;

fn main() {
    let n = io::stdin();
    let n = n.lock().lines().count();
    println!("{}", n);
}

#3

I mean, it works like this in many languages too; it’s just that you may have a GC around which makes it still work anyway.


#4

There’s this long-standing RFC for this class of issues: https://github.com/rust-lang/rfcs/blob/master/text/0066-better-temporary-lifetimes.md.


#5

Equivalent code (without type qualifiers fixed in place.)

count(lines(lock(io::stdin())));

Once the call to lock function completes its return value is passes as parameter to lines but all parameters used passed to lock stop being in use. Locks return value though is linked to the lifetime of its parameter.


#6

The drop check rule requires that the temporaries strictly outlive each other, it considers them to be of the same lifetime in this case.

If you’d construct a similar chain but where none of the parts need the drop check rule, then it would compile. Like this:

"abc".to_string().to_string().chars().count()

In your case it must be the StdinLock (Contains a MutexGuard) that triggers it I think?


#7

Ah, thanks for the reference to the drop check rule. That makes a lot of sense. It seems, then, that this example doesn’t need RFC 66; it would only need temporary values to have distinct, nested lifetimes—a smaller change. …Right?

In any case the current error message is not great. There’s no good place for the “temporary value dropped here” message to point to.


Bug in borrow checker?
#8

I have a detailed discussion of a similar case here