Skip to content

Writing valid UTF-8 to the Windows terminal using stdout().write() can incorrectly return an error #83258

Closed
@Count-Count

Description

@Count-Count

I tried this code:

use std::io::Write;

fn standard_write_all<W: std::io::Write>(mut w: W, mut buf: &[u8]) -> Result<(), std::io::Error> {
    while !buf.is_empty() {
        match w.write(buf) {
            Ok(0) => {
                return Err(std::io::Error::new(
                    std::io::ErrorKind::WriteZero,
                    "failed to write whole buffer",
                ));
            }
            Ok(n) => buf = &buf[n..],
            Err(ref e) if e.kind() == std::io::ErrorKind::Interrupted => {}
            Err(e) => return Err(e),
        }
    }
    Ok(())
}

fn main() {
    let poison = "Text here does not matter.\n".to_owned() + "x".repeat(1024 - 1).as_str() + "😀";

    // The special stdout().write_all() impl works.
    std::io::stdout().write_all(poison.as_bytes()).unwrap();
    println!();
    println!();

    // A standard write_all() implementation fails.
    //
    // 1. First write call
    // The bytes up to the newline are passed through to the console in one write.
    //
    // The next 1023 b'x' bytes and the first byte of "😀" are buffered leading to
    // an incomplete codepoint at the end of the buffer. The buffer is now poisoned
    // meaning that the next write() call will fail even if the missing UTF-8 bytes
    // are supplied.
    //
    // 2. Second write call
    // The buffer is flushed completely on the next call, the console writes 1023 b'x' bytes
    // and is then forced to write the remaining byte from the incomplete UTF-8 sequence.
    standard_write_all(std::io::stdout(), poison.as_bytes()).unwrap();
}

I expected to see this happen: Program does not panic.

Instead, this happened: Program panics with the error Windows stdio in console mode does not support writing non-UTF-8 byte sequences.

Meta

rustc --version --verbose:

rustc 1.50.0 (cb75ad5db 2021-02-10)
binary: rustc
commit-hash: cb75ad5db02783e8b0222fee363c5f63f7e2cf5b
commit-date: 2021-02-10
host: x86_64-pc-windows-msvc
release: 1.50.0
Backtrace

thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Custom { kind: InvalidData, error: "Windows stdio in console mode does not support writing non-UTF-8 byte sequences" }', src\main.rs:35:65
stack backtrace:
   0: std::panicking::begin_panic_handler
             at /rustc/cb75ad5db02783e8b0222fee363c5f63f7e2cf5b\/library\std\src\panicking.rs:493
   1: core::panicking::panic_fmt
             at /rustc/cb75ad5db02783e8b0222fee363c5f63f7e2cf5b\/library\core\src\panicking.rs:92
   2: core::option::expect_none_failed
             at /rustc/cb75ad5db02783e8b0222fee363c5f63f7e2cf5b\/library\core\src\option.rs:1268
   3: core::result::Result<tuple<>, std::io::error::Error>::unwrap<tuple<>,std::io::error::Error>
             at C:\Users\hans\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib\rustlib\src\rust\library\core\src\result.rs:973
   4: termcolorbug::main
             at .\src\main.rs:35
   5: core::ops::function::FnOnce::call_once<fn(),tuple<>>
             at C:\Users\hans\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib\rustlib\src\rust\library\core\src\ops\function.rs:227
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.

Update

To clarify: This problem occurs with all Write trait implementations that don't override the default Write::write_all() implementation, e.g. with this simple DelegatingWrite:

use std::io::Write;

struct DelegatingWrite<W> {
    delegate: W,
}

impl<W: Write> DelegatingWrite<W> {
    fn new(delegate: W) -> DelegatingWrite<W> {
        DelegatingWrite { delegate }
    }
}

impl<W: Write> Write for DelegatingWrite<W> {
    fn write(&mut self, b: &[u8]) -> std::io::Result<usize> {
        self.delegate.write(b)
    }

    fn flush(&mut self) -> std::io::Result<()> {
        self.delegate.flush()
    }
}

fn main() {
    let poison = "Text here does not matter.\n".to_owned() + "x".repeat(1024 - 1).as_str() + "😀";
    let mut w = DelegatingWrite::new(std::io::stdout());
    write!(w, "{}", poison).unwrap(); // <-- panics
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-UnicodeArea: UnicodeA-ioArea: `std::io`, `std::fs`, `std::net` and `std::path`C-bugCategory: This is a bug.O-windowsOperating system: WindowsT-libs-apiRelevant to the library API team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions