Why Write trait required

The following code works to repeatedly prompt a user for input.
But I don't understand why I have to pull in the Write trait when my code doesn't explicitly use it.

use std::io::{stdin, stdout, Write}; // can't call flush below unless Write is included here - Why?

fn main() {
  let mut buffer = String::new();

  loop {
    print!("Command: ");
    // The flush method returns a Result enum value.
    // If it is Ok, the expect method returns the value it contains.
    // If it is Err, it panics with the supplied message.
    stdout().flush().expect("failed to flush");

    // The read_line method also returns a Result enum value.
    stdin().read_line(&mut buffer).expect("failed to read");

    buffer.pop(); // removes newline from end of buffer

    if buffer == "quit" {
      break;
    }

    println!("You entered {}.", buffer);

    buffer.clear(); // prepares to reuse buffer
  }
}

flush is a method of the Write trait, rather than an inherent method of std::io::StdOut. Rust requires the trait definition to be in scope to call its methods on an object.

This is a usability tradeoff made to allow crates to define trait implementations for objects they didn't define. Otherwise, if some other trait were to define a flush method and be implemented for StdOut, the code you've shown here would be ambiguous. With this rule, linking in a new crate can't break existing code-- the affected code has to explicitly use the two definitions for there to be a conflict.

As an alternative, you can always use the unwieldy fully-qualified syntax to make the call:

std::io::Write::flush(&mut stdio()).expect("failed to flush");
4 Likes

I think I understand your point about allowing crates to define trait implementations for objects they didn't define. Is that really what is happening here? Is the flush method for the object returned by the stdout method really defined in a different crate than the one that defines that object? This seems like such a fundamental part of stdout that I would have expected it to be defined in the same crate.

No, the Write implementation for StdOut is defined directly after the struct declaration. But flush is a trait method because it's applicable to any kind of output data stream.

The separate crates explanation is a justification for the general rule that to use the method-call shorthand, the trait that defines the method must be in scope. In practice, you'll almost always want stdout().flush() to refer to Write::flush(), but it's not as clear for some other object-trait pairs. The language designers decided in this case that it's better to have a uniformly-applied rule than an exception that only sometimes applies.

3 Likes

Does my print_flush function below seem like a reasonable way to simplify repeated usages where I want to print a string without a newline and flush it? I'm surprised something like this isn't built-in, but maybe there are issues I'm not considering.

use std::io::{self, Write};

// To use this, add the following to the dependencies in Cargo.toml:
// text_io = "0.1.8"
use text_io::read;

fn print_flush(text: &str) {
  let mut stdout = io::stdout();
  stdout.write(text.as_bytes()).unwrap();
  stdout.flush().unwrap();
}

fn main() {
  loop {
    print_flush("Command: ");
    let command: String = read!("{}\n"); // reads until newline and omits it
    if command == "quit" {
      break;
    }
    println!("You entered {}.", command);
  }
}

RFC 2375 proposes addition of "inherent traits". Implementing such trait will be possible only for types defined in the same crate and you will be able to use its methods without importing it.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.