Code change:
<!-- Thank you for filing a bug report! Please provide a short summary of the bug, along with any information you feel relevant to replicating the bug. --I tried this code: I expected to see this happen: Program does not panic. Instead, this happened: Program panics with the error . <!-- If you're using the stable version of the compiler, you should also check if the bug also exists in the beta or nightly versions. --: <!-- Include a backtrace in the code block by setting in your environment. E.g. . --<details<summaryBacktrace</summary<p</p</detailsTo clarify: This problem occurs with all trait implementations that don't override the default implementation, e.g. with this simple DelegatingWrite:
I see two distinct possibilites to resolve this: 1) Treat incomplete s not ending on a codepoint boundary as a bug and fix that in the stdlib. Also clarify in the docs that indivual calls must be passed complete UTF-8 sequences if a Windows terminal is attached. 2) In the vain of "Be liberal in what you accept" handle incomplete UTF-8 passed in via calls appropriately, as long as the sequence is completed properly in the next call(s).
Hm, so this is ultimately a bug in the buffer uses? It attempts to write 1024 bytes but only 1023 bytes can written in the first OS call. It then has a leftover byte, which I guess it tries to write by itself thus causing this error?
The logic which errors if incomplete UTF-8 is passed in at index 0 is in the std lib at . That is where the error message comes from. As far as I can see the stdio buffer implementation (in stable, it is in , apparently currently being revamped) can not know of the requirement that only complete UTF-8 may be written to the underlying Windows terminal so I think that it should be encapsulated in by just accepting and buffering incomplete UTF-8 when necessary.
I mean, a generic buffer can attempt to avoid small writes (say, less than 16 bytes) by copying those left over bytes to the front of the buffer and waiting for either another to refill the buffer or a to empty it. But I see what you mean. Introducing a small buffer to windows/stdio would fix the problem.

Related discussion:
text: return ret; } <del> // As the console is meant for presenting text, we assume bytes of `data` come from a string // and are encoded as UTF-8, which needs to be encoded as UTF-16. </del> <ins> if incomplete_utf8.len > 0 { assert!( incomplete_utf8.len < 4, "Unexpected number of bytes for incomplete UTF-8 codepoint." ); if data[0] >> 6 != 0b10 { incomplete_utf8.len = 0; // not a continuation byte - reject return Err(io::Error::new_const( io::ErrorKind::InvalidData, &"Windows stdio in console mode does not support writing non-UTF-8 byte sequences", )); } incomplete_utf8.bytes[incomplete_utf8.len as usize] = data[0]; incomplete_utf8.len += 1; let char_width = utf8_char_width(incomplete_utf8.bytes[0]); if (incomplete_utf8.len as usize) < char_width { // more bytes needed return Ok(1); } let s = str::from_utf8(&incomplete_utf8.bytes[0..incomplete_utf8.len as usize]); incomplete_utf8.len = 0; match s { Ok(s) => { assert_eq!(char_width, s.len()); let written = write_valid_utf8_to_console(handle, s)?; assert_eq!(written, s.len()); // guaranteed by write_valid_utf8_to_console() for single codepoint writes return Ok(1); } Err(_) => { return Err(io::Error::new_const( io::ErrorKind::InvalidData, &"Windows stdio in console mode does not support writing non-UTF-8 byte sequences", )); } } } // As the console is meant for presenting text, we assume bytes of `data` are encoded as UTF-8, // which needs to be encoded as UTF-16. </ins> // // If the data is not valid UTF-8 we write out as many bytes as are valid. <del> // Only when there are no valid bytes (which will happen on the next call), return an error. </del> <ins> // If the first byte is invalid it is either first byte of a multi-byte sequence but the // provided byte slice is too short or it is the first byte of an invalide multi-byte sequence. </ins> let len = cmp::min(data.len(), MAX_BUFFER_SIZE / 2); let utf8 = match str::from_utf8(&data[..len]) { Ok(s) => s, Err(ref e) if e.valid_up_to() == 0 => { <del> return Err(io::Error::new_const( io::ErrorKind::InvalidData, &"Windows stdio in console mode does not support writing non-UTF-8 byte sequences", )); </del> <ins> let first_byte_char_width = utf8_char_width(data[0]); if first_byte_char_width > 1 && data.len() < first_byte_char_width { incomplete_utf8.bytes[0] = data[0]; incomplete_utf8.len = 1; return Ok(1); } else { return Err(io::Error::new_const( io::ErrorKind::InvalidData, &"Windows stdio in console mode does not support writing non-UTF-8 byte sequences", )); } </ins> } Err(e) => str::from_utf8(&data[..e.valid_up_to()]).unwrap(), }; <ins> write_valid_utf8_to_console(handle, utf8) } fn write_valid_utf8_to_console(handle: c::HANDLE, utf8: &str) -> io::Result<usize> { </ins> let mut utf16 = [0u16; MAX_BUFFER_SIZE / 2]; let mut len_utf16 = 0; for (chr, dest) in utf8.encode_utf16().zip(utf16.iter_mut()) {