有没有一种方法可以在构造函数中使用锁定的标准输入和输出,使其与您正在构造的结构一样长?

Is there a way to use locked standard input and output in a constructor to live as long as the struct you're constructing?

我正在构建一个可以连续提出一系列问题的 PromptSet。出于测试原因,它允许您传递 reader 和编写器,而不是直接使用标准输入和标准输出。

因为 stdin 和 stdout 是常见的用例,我想创建一个默认值 "constructor",允许用户生成 PromptSet<StdinLock, StdoutLock> 而无需任何参数。到目前为止,这是代码:

use std::io::{self, BufRead, StdinLock, StdoutLock, Write};

pub struct PromptSet<R, W>
where
    R: BufRead,
    W: Write,
{
    pub reader: R,
    pub writer: W,
}

impl<R, W> PromptSet<R, W>
where
    R: BufRead,
    W: Write,
{
    pub fn new(reader: R, writer: W) -> PromptSet<R, W> {
        return PromptSet {
            reader: reader,
            writer: writer,
        };
    }

    pub fn default<'a>() -> PromptSet<StdinLock<'a>, StdoutLock<'a>> {
        let stdin = io::stdin();
        let stdout = io::stdout();

        return PromptSet {
            reader: stdin.lock(),
            writer: stdout.lock(),
        };
    }

    pub fn prompt(&mut self, question: &str) -> String {
        let mut input = String::new();

        write!(self.writer, "{}: ", question).unwrap();
        self.writer.flush().unwrap();
        self.reader.read_line(&mut input).unwrap();

        return input.trim().to_string();
    }
}

fn main() {}

StdinLockStdoutLock 都需要声明生命周期。更复杂的是,我认为原始的 stdin()/stdout() 句柄需要至少与锁一样长。我希望对 StdinLockStdoutLock 的引用与我的 PromptSet 一样长,但无论我尝试什么,我都无法让它工作。这是我不断收到的错误:

error[E0597]: `stdin` does not live long enough
  --> src/main.rs:30:21
   |
30 |             reader: stdin.lock(),
   |                     ^^^^^ borrowed value does not live long enough
...
33 |     }
   |     - borrowed value only lives until here
   |
note: borrowed value must be valid for the lifetime 'a as defined on the method body at 25:5...
  --> src/main.rs:25:5
   |
25 | /     pub fn default<'a>() -> PromptSet<StdinLock<'a>, StdoutLock<'a>> {
26 | |         let stdin = io::stdin();
27 | |         let stdout = io::stdout();
28 | |
...  |
32 | |         };
33 | |     }
   | |_____^

error[E0597]: `stdout` does not live long enough
  --> src/main.rs:31:21
   |
31 |             writer: stdout.lock(),
   |                     ^^^^^^ borrowed value does not live long enough
32 |         };
33 |     }
   |     - borrowed value only lives until here
   |
note: borrowed value must be valid for the lifetime 'a as defined on the method body at 25:5...
  --> src/main.rs:25:5
   |
25 | /     pub fn default<'a>() -> PromptSet<StdinLock<'a>, StdoutLock<'a>> {
26 | |         let stdin = io::stdin();
27 | |         let stdout = io::stdout();
28 | |
...  |
32 | |         };
33 | |     }
   | |_____^

我完全有可能不理解生命周期的概念或其他超级基础的概念。

lock 方法的签名是 fn lock(&self) -> StdinLock,当使用生命周期注释完全扩展时,它是 fn lock<'a>(&'a self) -> StdinLock<'a>。因此,StdinLock 只能与调用 lock 方法的值一样长。由于您在此函数中定义了 stdin,因此 StdinLock 无法比该函数更有效。这与 returning a reference to a local value. You also .

相同

你不能这样做,也无法解决它。唯一的解决方法是让 default 方法将 StdinStdout 对象作为参数。

也就是说,您可以解决它。是的,我知道,我刚刚说的恰恰相反,但它更像是 "no one other than me will ever use stdin/stdout" (a.k.a., println! 将不再起作用!) .

在 Rust 1.26 中,您可以使用 Box::leak to leak the Stdin to a &'static Stdin, which will yield a StdinLock<'static>. Before Rust 1.26, you can use the leak crate:

pub fn default() -> PromptSet<StdinLock<'static>, StdoutLock<'static>> {
    let stdin = Box::leak(Box::new(io::stdin()));
    let stdout = Box::leak(Box::new(io::stdout()));

    PromptSet {
        reader: stdin.lock(),
        writer: stdout.lock(),
    }
}

可能不是您问题的真正答案,而是类似问题的答案。这是我的解决方案。

这里的主要技巧是为每一行调用 stdin.lock()

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

struct StdinWrapper {
    stdin: Stdin,
}

impl Iterator for StdinWrapper {
    type Item = String;

    fn next(&mut self) -> Option<Self::Item> {
        let stdin = &self.stdin;
        let mut lines = stdin.lock().lines();
        match lines.next() {
            Some(result) => Some(result.expect("Cannot read line")),
            None => None,
        }
    }
}

/**
 * Callers of this method should not know concrete source of the strings.
 * It could be Stdin, a file, DB, or even aliens from SETI.
 */
fn read() -> Box<Iterator<Item = String>> {
    let stdin = io::stdin();
    Box::new(StdinWrapper { stdin })
}

fn main() {
    let lines = read();

    for line in lines {
        println!("{}", line);
    }
}