So I have lots of API that build up strings piece by piece. The easiest way to do it is to return a String. For example, url::form_urlencoded::serialize does this.
One issue with this approach appears when one wants to compose such functions. You might want to have, say, a form-urlencoded query string in the middle of an URL. Intermediate String return values each have their own memory allocation, which is wasteful.
Finally, what if the ultimate goal is not to obtain a single string in memory but write text to a file, or push it through a pipeline that processes it incrementally? The String::push_str method can be abstracted into a trait, and API generating text be made generic over implementations of that trait. This is similar to the std::io::Write trait, but with Unicode strings instead of bytes. Iāve made a TextWriter trait for this. (See it used in e.g. cssparser::serializer.)
std::fmt::Write
The formatting system (behind the format! macro and the Display trait) has exactly the same requirements, and the std::fmt::Write trait was introduced for the same reasons. It is almost identical to TextWriter, except that the documentation discourage its use:
This is similar to the standard library's io::Write trait, but it is only intended for use in libcore.
This trait should generally not be implemented by consumers of the standard library. The write! macro accepts an instance of io::Write, and the io::Write trait is favored over implementing this trait.
Proposals
Iād like to propose three changes. But the conventions here are more important than the actual code changes.
āApproveā of using std::fmt::Write other than for the formatting system by removing the quoted bits of documentation. It feels silly to maintain a trait almost identical to one in the standard library just because of a convention not to use the latter.
To further show that they are not necessarily linked, move the trait out of std::fmt. Iām not sure what module is a good destination, maybe std::str? Edit: Not std::io, I think. I/O is really bytes.
Optional: Add a write_char method to the trait, with a default implementation based on char::encode_utf8 and the write_str method. This is not strictly necessary (one can always use write!(w, "{}", c)), but I donāt see a reason not to have it. char is on of Rustās primitive types.
A possibly important difference is lack of flush in std::fmt::Write. This may be understood as a feature to make general-purpose formatted output as simple and flexible as possible (e.g. stdout/stderr may flush automatically on newlines), while flush may be provided by other traits or simply type-specific methods. But the case of a āstrings-only writer with flushā should be possible to express in trait bounds.
Iām on the same page as @alexcrichton ā Iām wary only about moving it.
The main reason Iām hesitant to move it is that Iād like to be very confident that this is the TextWriter trait we want before we stabilize it in a āgoodā location. Simon, youāve given this a lot more thought than I have, and if you are reasonably satisfied that this covers our needs there, Iām open to it.
@alexcrichton, I assume this has to be stable for our various formatting macros to work?
I think actually we could get by without the trait being stable. This means that you could not call the fmt::write function but that's pretty rare anyway. Most interactions with std::fmt are either through fmt::Formatter (stable) or std::io::Write::write_fmt (stable).
So de-stabilising this trait may actually be a good idea.
Ok, fair enough. I can also start using it where it is.
So far Iāve mostly used and wanted to use this for serializing things to a given file format. This is only some of the use cases, so I canāt really talk about all of everyoneās use cases.
This said Iām reasonably confident about they write_str and write_fmt methods as they exist today ā there just isnāt that many possible ways to design them. We can always add more features (including more info in fmt::Error) later backward-compatibly, right?
This would defeat the point of saying itās OK to use this trait outside of the formatting system, if we want most of the ecosystem to use stable Rust by March 9.
So, the main goals of "blessing" a trait in std would be to (1) enable interop between various libraries and (2) provide basic building blocks in std itself.
Going forward, our hope is to gain experience and buy-in with abstractions in crates.io first, and then move things into std as they are becoming de facto standards (or in some cases to help move things in that direction).
I think the idea of destabilizing fmt::Write is not to encourage people to use it there (which would require using nightlies), but instead to allow a separate version of the trait (and other infrastructure) to be developed in crates.io and then later replacefmt::Write if/when we move it into std. Whereas if we ship a stable trait now, we won't be able to iterate as much on its design.
@SimonSapin, what do you think of exploring this space and gaining buy-in in crates.io first, and leaving room to replace fmt::Write with the resulting design later on?
https://crates.io/crates/text_writer has existed for a while, and this thread was to discuss whether we have reached that ālaterā point. Though admittedly Iām not aware of code Iām not involved with using this crate.