Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When returning the outcome of consuming a StdinLock, why was the borrow to stdin retained?

Tags:

rust

lifetime

Given the following function:

use std::io::{BufRead, stdin};

fn foo() -> usize {
    let stdin = stdin();
    let stdinlock = stdin.lock();
    stdinlock
        .lines()
        .count()
}

This fails to compile with the following error:

error: `stdin` does not live long enough
  --> src/main.rs:12:1
   |
7  |     let stdinlock = stdin.lock();
   |                     ----- borrow occurs here
...
11 | }
   | ^ `stdin` dropped here while still borrowed
   |
   = note: values in a scope are dropped in the opposite order they are created

I find this surprising because the outcome of consuming the lock (via lines) does not retain any references to the original source. In fact, assigning the same outcome to a binding before returning works just fine (Playground).

fn bar() -> usize {
    let stdin = stdin();
    let stdinlock = stdin.lock();
    let r = stdinlock
        .lines()
        .count();
    r
}

This suggests that returning a "consumed lock" immediately has led to the lock attempting to live longer than the locked content, much in an unusual way. All references that I looked into usually point out that the order of declaration matters, but not how the returned objects can affect the order in which they are released.

So why is the former function rejected by the compiler? Why is the lock being seemingly retained for longer than expected?

like image 532
E_net4 stands with Ukraine Avatar asked Apr 24 '17 14:04

E_net4 stands with Ukraine


2 Answers

This seems to be a bug in the compiler. You can make the compiler happy by using an explicit return statement:

use std::io::{stdin, BufRead};

fn foo() -> usize {
    let stdin = stdin();
    let stdinlock = stdin.lock();
    return stdinlock
        .lines()
        .count();
}

fn main() {}

playground

As mentioned in the comments, there are multiple Rust issues related to this:

  • 37407
  • 21114
like image 162
musicmatze Avatar answered Oct 11 '22 22:10

musicmatze


I cannot answer the why of your question, but I can state that the current1 implementation of non-lexical lifetimes allows the original code to compile:

#![feature(nll)]

use std::io::{BufRead, stdin};

fn foo() -> usize {
    let stdin = stdin();
    let stdinlock = stdin.lock();
    stdinlock
        .lines()
        .count()
}

Playground

1 1.25.0-nightly (2018-01-11 73ac5d6)

like image 28
Shepmaster Avatar answered Oct 11 '22 21:10

Shepmaster