Change:
Given the following code: The current output is: I think it's typical for a newcomer to want to get a iterator by chaining method calls like . There's no obvious reason why that shouldn't work. The examples in the documentation do typically show being assigned to a variable prior to calling its method, but don't explain why that's necessary. I think the "borrow" terminology is misleading, but there might not be a concise way to describe what's actually going on to the user. The "temporary value is freed at the end of this statement" is probably correct. The "creates a temporary which is freed while still in use" is probably more correctly "creates a temporary that is required to outlive (or live exactly as long as) the result of...", followed by a pointer to the method invocation, with text of " method call". Maybe it should also point out the lifetime argument in the return type. I tried and failed to find a satisfactory explanation of what exactly was borrowing from the temporary value produced by . When I looked through the library source code, the only references in were those created by locking a , and the in question is static. I'm guessing the existence of a borrow is assumed rather than inferred from analyzing the function body of . The assumption might be the result of the anonymous lifetime argument on the output type from , which imposes a requirement that the doesn't outlive (or has the same lifetime as?) the value that is the receiver of . The correct fix might be to change the locked stdin handles to all use lifetimes, but I will probably file that as a separate issue.
label +A-borrow-checker +A-lifetimes +D-confusing +D-incorrect +D-newcomer-roadblock
IMO the intent is clear here. Without changing the API, why exactly is Rust unable to lift 'a &'static X to 'static & 'static X, if X also contains only 'static lifetimes? I don't think raw references have a meaningful dropper; borrowck is able to freely resolve any derived lifetimes on impls that don't depend on anything else to 'static; I'm pretty sure under current rules we should be able to substitute latter for earlier and get exactly the same behaviour.
This is due to . is declared as follows: Because there is only one input lifetime (the elided lifetime of ), it is assigned to the output lifetime , which is why the lock can live at most as long as the temporary.
Thanks. I think this might be a bug in libstd (using instead of as the return type), but I'll file that separately. What I think is a diagnostic inaccuracy is that the error message refers to a borrow, when, by inspection of the library source code, no borrow is taking place in . (Unless I'm missing something?) What I think is happening is that the compiler is enforcing that lives no longer than the temporary produced by , and is misleadingly calling that a borrow in the error message.
As a simplified example: The error message refers to a borrow even when the output type of does not refer to at all!
that's because of the elision rules: on the declaration you're implicitly telling the compiler that the lifetime of the borrow matches the lifetime of the type. .
Thanks. I think the error messages could use a little more detail about how something that the borrow checker sees as a borrow may be an artifact of lifetime constraints in a function signature rather than an actual borrow in the function body. It certainly surprised me the first time I ran into it. (Maybe only in the explanatory text in the compiler error index. It looks like E0597 could also benefit from that detail, because it's a similar case, and an example in E0716 produces E0597.)
Shorter example, probably better suited for an example in the compiler error index:

Discussion:
pub use self::stdio::set_output_capture; #[stable(feature = "rust1", since = "1.0.0")] pub use self::stdio::{stderr, stdin, stdout, Stderr, Stdin, Stdout}; <ins> #[unstable(feature = "stdio_locked", issue = "none")] pub use self::stdio::{stderr_locked, stdin_locked, stdout_locked}; </ins> #[stable(feature = "rust1", since = "1.0.0")] pub use self::stdio::{StderrLock, StdinLock, StdoutLock}; #[unstable(feature = "print_internals", issue = "none")]

} } <ins> /// Constructs a new locked handle to the standard input of the current /// process. /// /// Each handle returned is a guard granting locked access to a shared /// global buffer whose access is synchronized via a mutex. If you need /// more explicit control over locking, for example, in a multi-threaded /// program, use the [`io::stdin`] function to obtain an unlocked handle, /// along with the [`Stdin::lock`] method. /// /// The lock is released when the returned guard goes out of scope. The /// returned guard also implements the [`Read`] and [`BufRead`] traits for /// accessing the underlying data. /// /// **Note**: The mutex locked by this handle is not reentrant. Even in a /// single-threaded program, calling other code that accesses [`Stdin`] /// could cause a deadlock or panic, if this locked handle is held across /// that call. /// /// ### Note: Windows Portability Consideration /// When operating in a console, the Windows implementation of this stream does not support /// non-UTF-8 byte sequences. Attempting to read bytes that are not valid UTF-8 will return /// an error. /// /// # Examples /// /// ```no_run /// #![feature(stdio_locked)] /// use std::io::{self, Read}; /// /// fn main() -> io::Result<()> { ///     let mut buffer = String::new(); ///     let mut handle = io::stdin_locked(); /// ///     handle.read_to_string(&mut buffer)?; ///     Ok(()) /// } /// ``` #[unstable(feature = "stdio_locked", issue = "none")] pub fn stdin_locked() -> StdinLock<'static> { stdin().into_locked() } </ins> impl Stdin { /// Locks this handle to the standard input stream, returning a readable /// guard.

/// ``` #[stable(feature = "rust1", since = "1.0.0")] pub fn lock(&self) -> StdinLock<'_> { <del> StdinLock { inner: self.inner.lock().unwrap_or_else(|e| e.into_inner()) } </del> <ins> self.lock_any() </ins> } /// Locks this handle and reads a line of input, appending it to the specified buffer.

pub fn read_line(&self, buf: &mut String) -> io::Result<usize> { self.lock().read_line(buf) } <ins> // Locks this handle with any lifetime. This depends on the // implementation detail that the underlying `Mutex` is static. fn lock_any<'a>(&self) -> StdinLock<'a> { StdinLock { inner: self.inner.lock().unwrap_or_else(|e| e.into_inner()) } } /// Consumes this handle to the standard input stream, locking the /// shared global buffer associated with the stream and returning a /// readable guard. /// /// The lock is released when the returned guard goes out of scope. The /// returned guard also implements the [`Read`] and [`BufRead`] traits /// for accessing the underlying data. /// /// It is often simpler to directly get a locked handle using the /// [`stdin_locked`] function instead, unless nearby code also needs to /// use an unlocked handle. /// /// # Examples /// /// ```no_run /// #![feature(stdio_locked)] /// use std::io::{self, Read}; /// /// fn main() -> io::Result<()> { ///     let mut buffer = String::new(); ///     let mut handle = io::stdin().into_locked(); /// ///     handle.read_to_string(&mut buffer)?; ///     Ok(()) /// } /// ``` #[unstable(feature = "stdio_locked", issue = "none")] pub fn into_locked(self) -> StdinLock<'static> { self.lock_any() } </ins> } #[stable(feature = "std_debug", since = "1.16.0")]

} } <ins> /// Constructs a new locked handle to the standard output of the current /// process. /// /// Each handle returned is a guard granting locked access to a shared /// global buffer whose access is synchronized via a mutex. If you need /// more explicit control over locking, for example, in a multi-threaded /// program, use the [`io::stdout`] function to obtain an unlocked handle, /// along with the [`Stdout::lock`] method. /// /// The lock is released when the returned guard goes out of scope. The /// returned guard also implements the [`Write`] trait for writing data. /// /// ### Note: Windows Portability Consideration /// When operating in a console, the Windows implementation of this stream does not support /// non-UTF-8 byte sequences. Attempting to write bytes that are not valid UTF-8 will return /// an error. /// /// # Examples /// /// ```no_run /// #![feature(stdio_locked)] /// use std::io::{self, Write}; /// /// fn main() -> io::Result<()> { ///     let mut handle = io::stdout_locked(); /// ///     handle.write_all(b"hello world")?; /// ///     Ok(()) /// } /// ``` #[unstable(feature = "stdio_locked", issue = "none")] pub fn stdout_locked() -> StdoutLock<'static> { stdout().into_locked() } </ins> pub fn cleanup() { if let Some(instance) = STDOUT.get() { // Flush the data and disable buffering during shutdown

/// ``` #[stable(feature = "rust1", since = "1.0.0")] pub fn lock(&self) -> StdoutLock<'_> { <ins> self.lock_any() } // Locks this handle with any lifetime. This depends on the // implementation detail that the underlying `ReentrantMutex` is // static. fn lock_any<'a>(&self) -> StdoutLock<'a> { </ins> StdoutLock { inner: self.inner.lock() } } <ins> /// Consumes this handle to the standard output stream, locking the /// shared global buffer associated with the stream and returning a /// writable guard. /// /// The lock is released when the returned lock goes out of scope. The /// returned guard also implements the [`Write`] trait for writing data. /// /// It is often simpler to directly get a locked handle using the /// [`io::stdout_locked`] function instead, unless nearby code also /// needs to use an unlocked handle. /// /// # Examples /// /// ```no_run /// #![feature(stdio_locked)] /// use std::io::{self, Write}; /// /// fn main() -> io::Result<()> { ///     let mut handle = io::stdout().into_locked(); /// ///     handle.write_all(b"hello world")?; /// ///     Ok(()) /// } /// ``` #[unstable(feature = "stdio_locked", issue = "none")] pub fn into_locked(self) -> StdoutLock<'static> { self.lock_any() } </ins> } #[stable(feature = "std_debug", since = "1.16.0")]

} } <ins> /// Constructs a new locked handle to the standard error of the current /// process. /// /// This handle is not buffered. /// /// ### Note: Windows Portability Consideration /// When operating in a console, the Windows implementation of this stream does not support /// non-UTF-8 byte sequences. Attempting to write bytes that are not valid UTF-8 will return /// an error. /// /// # Example /// /// ```no_run /// #![feature(stdio_locked)] /// use std::io::{self, Write}; /// /// fn main() -> io::Result<()> { ///     let mut handle = io::stderr_locked(); /// ///     handle.write_all(b"hello world")?; /// ///     Ok(()) /// } /// ``` #[unstable(feature = "stdio_locked", issue = "none")] pub fn stderr_locked() -> StderrLock<'static> { stderr().into_locked() } </ins> impl Stderr { /// Locks this handle to the standard error stream, returning a writable /// guard.

/// ``` #[stable(feature = "rust1", since = "1.0.0")] pub fn lock(&self) -> StderrLock<'_> { <ins> self.lock_any() } // Locks this handle with any lifetime. This depends on the // implementation detail that the underlying `ReentrantMutex` is // static. fn lock_any<'a>(&self) -> StderrLock<'a> { </ins> StderrLock { inner: self.inner.lock() } } <ins> /// Locks and consumes this handle to the standard error stream, /// returning a writable guard. /// /// The lock is released when the returned guard goes out of scope. The /// returned guard also implements the [`Write`] trait for writing /// data. /// /// # Examples /// /// ``` /// #![feature(stdio_locked)] /// use std::io::{self, Write}; /// /// fn foo() -> io::Result<()> { ///     let stderr = io::stderr(); ///     let mut handle = stderr.into_locked(); /// ///     handle.write_all(b"hello world")?; /// ///     Ok(()) /// } /// ``` #[unstable(feature = "stdio_locked", issue = "none")] pub fn into_locked(self) -> StderrLock<'static> { self.lock_any() } </ins> } #[stable(feature = "std_debug", since = "1.16.0")]

use super::*; use crate::panic::{RefUnwindSafe, UnwindSafe}; <ins> use crate::sync::mpsc::sync_channel; </ins> use crate::thread; #[test]

let _a = stderr(); let _a = _a.lock(); } <ins> #[test] #[cfg_attr(target_os = "emscripten", ignore)] fn test_lock_stderr() { test_lock(stderr, stderr_locked); } #[test] #[cfg_attr(target_os = "emscripten", ignore)] fn test_lock_stdin() { test_lock(stdin, stdin_locked); } #[test] #[cfg_attr(target_os = "emscripten", ignore)] fn test_lock_stdout() { test_lock(stdout, stdout_locked); } // Helper trait to make lock testing function generic. trait Stdio<'a>: 'static where Self::Lock: 'a, { type Lock; fn lock(&'a self) -> Self::Lock; } impl<'a> Stdio<'a> for Stderr { type Lock = StderrLock<'a>; fn lock(&'a self) -> StderrLock<'a> { self.lock() } } impl<'a> Stdio<'a> for Stdin { type Lock = StdinLock<'a>; fn lock(&'a self) -> StdinLock<'a> { self.lock() } } impl<'a> Stdio<'a> for Stdout { type Lock = StdoutLock<'a>; fn lock(&'a self) -> StdoutLock<'a> { self.lock() } } // Helper trait to make lock testing function generic. trait StdioOwnedLock: 'static {} impl StdioOwnedLock for StderrLock<'static> {} impl StdioOwnedLock for StdinLock<'static> {} impl StdioOwnedLock for StdoutLock<'static> {} // Tests locking on stdio handles by starting two threads and checking that // they block each other appropriately. fn test_lock<T, U>(get_handle: fn() -> T, get_locked: fn() -> U) where T: for<'a> Stdio<'a>, U: StdioOwnedLock, { // State enum to track different phases of the test, primarily when // each lock is acquired and released. #[derive(Debug, PartialEq)] enum State { Start1, Acquire1, Start2, Release1, Acquire2, Release2, } use State::*; // Logging vector to be checked to make sure lock acquisitions and // releases happened in the correct order. let log = Arc::new(Mutex::new(Vec::new())); let ((tx1, rx1), (tx2, rx2)) = (sync_channel(0), sync_channel(0)); let th1 = { let (log, tx) = (Arc::clone(&log), tx1); thread::spawn(move || { log.lock().unwrap().push(Start1); let handle = get_handle(); { let locked = handle.lock(); log.lock().unwrap().push(Acquire1); tx.send(Acquire1).unwrap(); // notify of acquisition tx.send(Release1).unwrap(); // wait for release command log.lock().unwrap().push(Release1); } tx.send(Acquire1).unwrap(); // wait for th2 acquire { let locked = handle.lock(); log.lock().unwrap().push(Acquire1); } log.lock().unwrap().push(Release1); }) }; let th2 = { let (log, tx) = (Arc::clone(&log), tx2); thread::spawn(move || { tx.send(Start2).unwrap(); // wait for start command let locked = get_locked(); log.lock().unwrap().push(Acquire2); tx.send(Acquire2).unwrap(); // notify of acquisition tx.send(Release2).unwrap(); // wait for release command log.lock().unwrap().push(Release2); }) }; assert_eq!(rx1.recv().unwrap(), Acquire1); // wait for th1 acquire log.lock().unwrap().push(Start2); assert_eq!(rx2.recv().unwrap(), Start2); // block th2 assert_eq!(rx1.recv().unwrap(), Release1); // release th1 assert_eq!(rx2.recv().unwrap(), Acquire2); // wait for th2 acquire assert_eq!(rx1.recv().unwrap(), Acquire1); // block th1 assert_eq!(rx2.recv().unwrap(), Release2); // release th2 th2.join().unwrap(); th1.join().unwrap(); assert_eq!( *log.lock().unwrap(), [Start1, Acquire1, Start2, Release1, Acquire2, Release2, Acquire1, Release1] ); } </ins>