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
.
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.
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.
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.
This is probably worth opening an issue about.
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.