I initially had the following code for an Interface
trait for use in displaying text to a user and getting input back. This includes a separate InterfaceProvider
trait that passes a &mut Interface
to a supplied function, making it possible to ensure that any necessary startup & cleanup code ran before & after using the interface. (I haven't yet implemented any interfaces that need startup or cleanup, but I'm thinking ahead.)
use std::io::{self, BufRead, Write};
pub trait InterfaceProvider {
type Interface: Interface;
/// Run `func` with the associated `Interface`.
fn with_interface<F>(self, func: F) -> io::Result<()>
where
F: FnOnce(&mut Self::Interface) -> io::Result<()>;
}
pub trait Interface {
/// Display the given text in the interface.
fn show_output(&mut self, text: &str) -> io::Result<()>;
/// Read a line of input from the interface.
///
/// Returns `None` on end of input.
fn get_input(&mut self) -> io::Result<Option<String>>;
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct BasicInterface<R, W> {
reader: R,
writer: W,
wrote_prompt: bool,
wrote_last_output: bool,
}
impl<R, W> BasicInterface<R, W> {
pub fn new(reader: R, writer: W) -> Self {
BasicInterface {
reader,
writer,
wrote_prompt: false,
wrote_last_output: false,
}
}
}
impl<R: BufRead, W: Write> InterfaceProvider for BasicInterface<R, W> {
type Interface = Self;
fn with_interface<F>(mut self, func: F) -> io::Result<()>
where
F: FnOnce(&mut Self::Interface) -> io::Result<()>,
{
func(&mut self)
}
}
impl<R: BufRead, W: Write> Interface for BasicInterface<R, W> {
fn show_output(&mut self, text: &str) -> io::Result<()> {
if self.wrote_prompt {
writeln!(&mut self.writer)?;
}
if !text.is_empty() {
writeln!(&mut self.writer, "{text}")?;
self.wrote_last_output = true;
} else {
self.wrote_last_output = false;
}
Ok(())
}
fn get_input(&mut self) -> io::Result<Option<String>> {
if self.wrote_last_output {
writeln!(&mut self.writer)?;
}
write!(&mut self.writer, "> ")?;
self.writer.flush()?;
self.wrote_prompt = true;
let mut input = String::new();
if self.reader.read_line(&mut input)? != 0 {
Ok(Some(input))
} else {
// Force the start of a new line:
writeln!(&mut self.writer)?;
Ok(None)
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct StandardInterface;
impl InterfaceProvider for StandardInterface {
type Interface = BasicInterface<io::StdinLock<'static>, io::StdoutLock<'static>>;
fn with_interface<F>(self, func: F) -> io::Result<()>
where
F: FnOnce(&mut Self::Interface) -> io::Result<()>,
{
let mut iface = BasicInterface::new(io::stdin().lock(), io::stdout().lock());
func(&mut iface)
}
}
As you can see, InterfaceProvider::with_interface()
consumes the provider, as I wanted to keep things simple and lifetime-free at first. Now, however, I'm ready to make things complicated and lifetime-full, but I've gotten a bit over my head.
Specifically, I want to make the following changes:
-
Change the receiver of
with_interface()
to&mut self
(not&self
, as the interface may borrow some data from the provider, and the interface has to be mutable, so the provider should be as well) -
Split
BasicInterface
into:- a
BasicInterfaceProvider
that only implementsInterfaceProvider
and just stores the reader & writer - a
BasicInterface
that only implementsInterface
and stores mutable references to the provider's reader & writer plus the state fields
- a
-
Change the
Interface
forStandardInterface
to the newBasicInterface
, still parametrized by stdin & stdout
My best (as in, producing the fewest errors) attempt so far has been as follows, where I changed the with_interface()
receiver, split BasicInterface
apart, and tried (and failed) to define BasicInterfaceProvider::Interface
as a BasicInterface
parameterized by the lifetime of self
:
Attempt #1
use std::io::{self, BufRead, Write};
pub trait InterfaceProvider {
type Interface: Interface;
// CHANGED: Receiver changed from `self` to `&mut self`
fn with_interface<F>(&mut self, func: F) -> io::Result<()>
where
F: FnOnce(&mut Self::Interface) -> io::Result<()>;
}
pub trait Interface {
fn show_output(&mut self, text: &str) -> io::Result<()>;
fn get_input(&mut self) -> io::Result<Option<String>>;
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct BasicInterfaceProvider<R, W> {
reader: R,
writer: W,
}
impl<R, W> BasicInterfaceProvider<R, W> {
pub fn new(reader: R, writer: W) -> Self {
BasicInterfaceProvider { reader, writer }
}
}
impl<'a, R: BufRead, W: Write> InterfaceProvider for BasicInterfaceProvider<R, W> {
type Interface
= BasicInterface<'a, R, W>
where
Self: 'a;
fn with_interface<F>(&mut self, func: F) -> io::Result<()>
where
F: FnOnce(&mut Self::Interface) -> io::Result<()>,
{
let mut iface = BasicInterface::new(&mut self.reader, &mut self.writer);
func(&mut self)
}
}
#[derive(Debug, Eq, PartialEq)]
pub struct BasicInterface<'a, R, W> {
reader: &'a mut R,
writer: &'a mut W,
wrote_prompt: bool,
wrote_last_output: bool,
}
impl<'a, R, W> BasicInterface<'a, R, W> {
fn new(reader: &'a mut R, writer: &'a mut W) -> Self {
BasicInterface {
reader,
writer,
wrote_prompt: false,
wrote_last_output: false,
}
}
}
impl<R: BufRead, W: Write> Interface for BasicInterface<'_, R, W> {
fn show_output(&mut self, text: &str) -> io::Result<()> {
if self.wrote_prompt {
writeln!(&mut self.writer)?;
}
if !text.is_empty() {
writeln!(&mut self.writer, "{text}")?;
self.wrote_last_output = true;
} else {
self.wrote_last_output = false;
}
Ok(())
}
fn get_input(&mut self) -> io::Result<Option<String>> {
if self.wrote_last_output {
writeln!(&mut self.writer)?;
}
write!(&mut self.writer, "> ")?;
self.writer.flush()?;
self.wrote_prompt = true;
let mut input = String::new();
if self.reader.read_line(&mut input)? != 0 {
Ok(Some(input))
} else {
// Force the start of a new line:
writeln!(&mut self.writer)?;
Ok(None)
}
}
}
// Not yet sure how to declare the Interface associated type for StandardInterface
This fails to compile with:
error[E0207]: the lifetime parameter `'a` is not constrained by the impl trait, self type, or predicates
--> src/lib.rs:29:6
|
29 | impl<'a, R: BufRead, W: Write> InterfaceProvider for BasicInterfaceProvider<R, W> {
| ^^ unconstrained lifetime parameter
For more information about this error, try `rustc --explain E0207`.
I also tried the following, inspired by the unstable Pattern
trait and its Searcher
associated type. I changed the definition of InterfaceProvider::Interface
to type Interface<'a>: Interface<'a>
, changed the receiver for with_interface()
to &mut self
, added <'_>
to the &mut Self::Interface
argument to the FnOnce
passed to with_interface()
, split BasicInterface
apart, defined BasicInterfaceProvider::Interface<'a>
as a BasicInterface<'a, R, W>
, and added some lifetimes to the bounds of impl InterfaceProvider for BasicInterfaceProvider
based on the compiler's suggestions, but I still can't get it to compile.
Attempt #2
use std::io::{self, BufRead, Write};
pub trait InterfaceProvider {
type Interface<'a>: Interface<'a>;
fn with_interface<F>(&mut self, func: F) -> io::Result<()>
where
F: FnOnce(&mut Self::Interface<'_>) -> io::Result<()>;
}
pub trait Interface<'a> {
fn show_output(&mut self, text: &str) -> io::Result<()>;
fn get_input(&mut self) -> io::Result<Option<String>>;
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct BasicInterfaceProvider<R, W> {
reader: R,
writer: W,
}
impl<R, W> BasicInterfaceProvider<R, W> {
pub fn new(reader: R, writer: W) -> Self {
BasicInterfaceProvider { reader, writer }
}
}
impl<'a, R: BufRead + 'a, W: Write + 'a> InterfaceProvider for BasicInterfaceProvider<R, W>
where
Self: 'a,
{
type Interface<'a> = BasicInterface<'a, R, W>;
fn with_interface<F>(&mut self, func: F) -> io::Result<()>
where
F: FnOnce(&mut Self::Interface<'_>) -> io::Result<()>,
{
let mut iface = BasicInterface::new(&mut self.reader, &mut self.writer);
func(&mut iface)
}
}
#[derive(Debug, Eq, PartialEq)]
pub struct BasicInterface<'a, R, W> {
reader: &'a mut R,
writer: &'a mut W,
wrote_prompt: bool,
wrote_last_output: bool,
}
impl<'a, R, W> BasicInterface<'a, R, W> {
fn new(reader: &'a mut R, writer: &'a mut W) -> Self {
BasicInterface {
reader,
writer,
wrote_prompt: false,
wrote_last_output: false,
}
}
}
impl<'a, R: BufRead, W: Write> Interface<'a> for BasicInterface<'a, R, W> {
fn show_output(&mut self, text: &str) -> io::Result<()> {
if self.wrote_prompt {
writeln!(&mut self.writer)?;
}
if !text.is_empty() {
writeln!(&mut self.writer, "{text}")?;
self.wrote_last_output = true;
} else {
self.wrote_last_output = false;
}
Ok(())
}
fn get_input(&mut self) -> io::Result<Option<String>> {
if self.wrote_last_output {
writeln!(&mut self.writer)?;
}
write!(&mut self.writer, "> ")?;
self.writer.flush()?;
self.wrote_prompt = true;
let mut input = String::new();
if self.reader.read_line(&mut input)? != 0 {
Ok(Some(input))
} else {
// Force the start of a new line:
writeln!(&mut self.writer)?;
Ok(None)
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct StandardInterfaceProvider;
impl InterfaceProvider for StandardInterfaceProvider {
type Interface<'a> = BasicInterface<'a, io::StdinLock<'static>, io::StdoutLock<'static>>;
fn with_interface<F>(&mut self, func: F) -> io::Result<()>
where
F: FnOnce(&mut Self::Interface<'_>) -> io::Result<()>,
{
let mut stdin = io::stdin().lock();
let mut stdout = io::stdout().lock();
let mut iface = BasicInterface::new(&mut stdin, &mut stdout);
func(&mut iface)
}
}
This fails to compile with:
error[E0496]: lifetime name `'a` shadows a lifetime name that is already in scope
--> crates/advcore/src/interface.rs:34:20
|
30 | impl<'a, R: BufRead + 'a, W: Write + 'a> InterfaceProvider for BasicInterfaceProvider<R, W>
| -- first declared here
...
34 | type Interface<'a> = BasicInterface<'a, R, W>;
| ^^ lifetime `'a` already in scope
error[E0207]: the lifetime parameter `'a` is not constrained by the impl trait, self type, or predicates
--> crates/advcore/src/interface.rs:30:6
|
30 | impl<'a, R: BufRead + 'a, W: Write + 'a> InterfaceProvider for BasicInterfaceProvider<R, W>
| ^^ unconstrained lifetime parameter
Some errors have detailed explanations: E0207, E0496.
For more information about an error, try `rustc --explain E0207`.
I've tried minor variations on both of the above attempts, but I can't get anything that compiles. Help?