On 07/26/2013 11:37 PM, Alex Crichton wrote:
I recently looked into redesigning fmt!, and I wanted to post my ideas before
going all the way to ensure that I'm not missing out on anything.
Fabulous. There's a number of related info in #2249 and sub-bugs. Other
designs have been floated for this before as well, but I don't know
where they are.
== Today's state ==
As of today, fmt! works pretty well. It's a very useful function and will likely
be a very common thing in all rust code using libstd. It does have a few
drawbacks though:
1. It always returns ~str. This should not be the case, rather it should always
write into some form of stream (io::Writer/io::rt::Writer). This avoids any
unnecessary allocations.
Certainly the implementation should work by writing to a buffer and not
concatenating strings, and the core interface should take a Writer or
similar. The `let foo = fmt!(...);` use case is very convenient though.
2. If you're formatting via %X, then the value must have one, and only one type
(you can't implement %s for your own types).
3. Format specifiers are a predefined compiler constant. It would be pretty
awesome if libraries/programs could specify their own fmt! specifiers via
something like a #[fmt="name"] attribute
Maybe. This would likely require some deep magic. There maybe other
options for specifying format placeholders that don't require allocating
alphabetic characters for the task ala printf. I really think we should
be looking outside the printf box.
4. Currently fmt! results in a lot of code at the callsite. This is because the
string allocation and building the string are all implemented inline. Only
the actual conversions to strings are performed in function calls.
Yep. Reducing inline bloat is critical.
5. It's generally slow right now due to performing large numbers of intermediate
allocations. A printing function should perform 0 allocations.
There are probably other limitations, but those are the big ones I can think of.
== What I'd like to see ==
I'm envisioning a rustc that predefines two macros, fmt! and fprintf! (maybe
ffmt!). The former would be as it is today, returning ~str, and the latter would
take a stream instance to write information to. Then fmt! would be implemented
with a memory buffer to fprintf!. This would solve problem 1 (kinda, more down
below).
Furthermore, the codegen would be different. Format strings would still be
parsed at compile time, but the format string would be passed through instead of
passing around these Conv structures at runtime. This means that the actual
function will then take a string and a slice of values to print (the types of
the values are verified at compile-time still). This would solve problem 2.
I don't see why this solves problem 2 (overloading format specifiers for
different types). By passing the original string it will have to reparse
at runtime. I don't understand the reasoning here.
All printing will be done through traits. For example:
#[fmt="d"]
trait Signed {
// Emit to Formatter's stream using its parameters for
width/precision/etc.
fn fmt(&Self, &mut std::fmt::Formatter);
}
Anything which implements this trait can then be used with the '%d' format
specifier. Furthermore this means that you can create user-defined format
specifiers (via the fmt attribute). I would imaging that multi-char format
specifiers could be something like: "%(mytype)". This solves problems 3 and 4.
I'm not a fan of printf-style format specifiers in general, largely
because the specifiers have to be allocated in some way (fmt attribute)
so are not particularly extensible. I can't imagine how this fmt
attribute can be implemented in a general way using current compiler
mechanisms, and am in favor of looking to other languages for guidance
on how formatting should be done instead of leaning on crusty old C here.
I'd also do my best at the same time to remove any and all allocations, but it's
not guaranteed that the system today can do that. Right now I think that there's
limitations blocking this.
== Is this possible? ==
In my opinion, the way to go about this is not to use trait parameterization,
but rather trait objects. In order to avoid allocations, the functions should
also take &Trait. From what I can tell, however, this is fairly broken and most
operations don't work at all. Plus I don't think there's any coercion from
~Trait and @Trait to &Trait yet.
It does seem like &Trait has a role here. I'd like to see a firm idea of
what the runtime interface looks like. I imagine it can all be
encapsulated in a simple signature like `fn format(&mut io::Writer,
&[(&Formattable, FormatSpecifier)])`, where every chunk of the format
string and the values to insert into it are cast to a stack object and
paired with some metadata indicating how it should be formatted. Such an
interface doesn't have any room for type-specific format traits like
`Signed` though.
Regardless, for now I don't think that fprintf! can exist, but I do believe that
I can rewrite fmt! to use this new system of defining formats. I can also try to
open up specific bugs about &Traits to get them to a working conditions.
===
Does this sound like a sane replacement for fmt? All code today would
work the same as it used to, but in theory it would be a bit faster
and hopefully more powerful as well.
In my opinion it is not ambitious enough since it leaves the printf
style intact, but I have something of a vendetta against printf. It does
seem likely though that we can come up with a runtime interface that is
reasonably decoupled from the string-based specification of formats;
create a migration path from printf-style formatting to something better.
-Brian
_______________________________________________
Rust-dev mailing list
[email protected]
https://mail.mozilla.org/listinfo/rust-dev