Why doesn't the write! and writeln! macro call std::io::Write::write_fmt directly?

It is quite annoying when I have to manually write use std::io::Write every time I want to use writeln! on a file. Auto-import doesn't work because the call to write_fmt is hidden inside a macro. The error is also confusing for beginners, since it tells the user that write_fmt is missing instead of guiding user to std::io::Write.

Fun fact: writeln! isn't just for use with std::io::Write, it's also for use with core::fmt::Write, or any time that happens to have a write_fmt. It's just a little C++ template style compile-time duck typing to help cut down on the boilerplate when performing string formatting.

To answer your question more directly, it can't use std::io::Write because it's part of core, and has uses beyond just formatting into an io::Write.

1 Like

C++ must use duck-typing because it doesn't have traits. What prevents the Rust standard library from just implement another trait called WriteFmt?

That's kinda what fmt::Write is already. The goal of writeln is to be generic over both Write traits. Because io::Write is in std, and core and writeln are in core, trait coherence rules prevent any sort of trait-based solution here. Additionally, the most common use of writeln is to write to a fmt::Formatter, which has inherent methods that are identical in name function to the ones from fmt::Write, even though that's also implemented.

1 Like

What it should be implemented for? It can't be blanket-implemented over core::fmt::Write and std::io::Write, since this will lead to conflicting implementations; and it can't be blanket-implemented over only one of them, since this will not cover everything that is covered now.

1 Like

If I was to redesign Rust, I would make io::Write inherits from fmt::Write, but that would probably be a breaking change.

That would be very breaking. Requiring everything that implements io::Write to also manually implement fmt::Write obviously wouldn't work, and any blanket impl would cause types that already implement both to break, and would also run into issues around the impl for &mut io::Write on top of that.

That being said, there has been plenty of discussion in the past about creating some kind of core::io module, and that might be able to bridge the gap somehow.

That doesn't really make sense. They are separate traits for a reason: they are drastically different in practical use.

fmt::Write is essentially infallible and it can be in core; it deals with formatting UTF-8 encoded strings and not arbitrary bytes.

Meanwhile, io::Write has to know about I/O errors (pretty much by definition), so it's fallible; it can't be in core because it has to have methods such as read_to_end() that take non-core argument types; and finally, it's a general byte stream abstraction, which doesn't need (and shouldn't enforce) UTF-8 at all times.

Conflating these two would have catastrophic consequences for usability, because doing that would result in a whole set of unnecessary requirements on either side (string-only formatters would have to depend on std, not core, and start caring about dynamically impossible I/O errors, while generic I/O writers couldn't handle anything that is not UTF-8). It's a bad, bad idea.

1 Like

This is probably worth opening an issue about.