-
Notifications
You must be signed in to change notification settings - Fork 20
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[ACP] Provide an interface for creating instances of fmt::Formatter #286
Comments
FWIW the proposal that I recall seeing floated on i.r-l.o or reddit was something like |
Marking as accepted because we've already said as a team that closing rust-lang/rfcs#3394 (comment) is tantamount to accepting a Formatter creation ACP. I am still interested in coming back to consider the |
Thanks, I didn't see this this – Regarding I believe most users want to construct a new That being said: If a majority prefers an implementation that adds |
Wouldn't it be better to take no arguments in |
Small nit: This interface would allow for something that has both |
How would you feel about an alternative like this? #[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct FormattingOptions { … }
impl FormattingOptions {
pub fn new() -> Self;
pub fn sign_plus(&mut self, sign_plus: bool) -> &mut Self;
pub fn sign_minus(&mut self, sign_minus: bool) -> &mut Self;
pub fn zero_pad(&mut self, zero_pad: bool) -> &mut Self;
pub fn alternate(&mut self, alternate: bool) -> &mut Self;
pub fn fill(&mut self, fill: Option<char>) -> &mut Self;
pub fn alignment(&mut self, alignment: Option<Alignment>) -> &mut Self;
pub fn width(&mut self, width: Option<usize>) -> &mut Self;
pub fn precision(&mut self, precision: Option<usize>) -> &mut Self;
pub fn get_sign_plus(&self) -> bool;
pub fn get_sign_minus(&self) -> bool;
pub fn get_zero_pad(&self) -> bool;
pub fn get_alternate(&self) -> bool;
pub fn get_fill(&self) -> Option<char>;
pub fn get_alignment(&self) -> Option<Alignment>;
pub fn get_width(&self) -> Option<usize>;
pub fn get_precision(&self) -> Option<usize>;
}
impl<'a> Formatter<'a> {
pub fn new(write: &'a mut (dyn Write + 'a), options: FormattingOptions) -> Self;
pub fn with_options(&mut self, options: FormattingOptions) -> Self;
pub fn options(&self) -> FormattingOptions;
} Now the This makes it very clear that a |
The approach of having an ordinary and extensible options struct sounds great. One minor thing on pub fn options(&self) -> FormattingOptions; Today we have https://doc.rust-lang.org/std/fs/struct.File.html#method.options, which would suggest that we should instead have impl<'a> Formatter<'a> {
pub fn options() -> FormattingOptions;
} so that most people don't need to know the name of the builder type. I'm not sure what that would mean for the getter, though... |
@m-ou-se: Thanks, your approach is much cleaner and better than mine! I only have two small nitpicks.
Regarding @scottmcm's point about |
I don't mind adding impl FormattingOptions {
pub fn make_formatter<'a>(self, write: &'a mut (dyn Write + 'a)) -> Formatter<'a>;
// or `into_formatter` or `to_formatter` or whatever color you like your bikeshed
} to allow Especially I'm not really concerned about the point that @scottmcm brought up. A Having Having |
@scottmcm Note that
|
Adding these changes to your last proposal, the interface looks like this: #[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct FormattingOptions { … }
enum Sign {
Plus,
Minus
}
impl FormattingOptions {
pub fn new() -> Self;
pub fn sign(&mut self, sign: Option<Sign>) -> &mut Self;
pub fn zero_pad(&mut self, zero_pad: bool) -> &mut Self;
pub fn alternate(&mut self, alternate: bool) -> &mut Self;
pub fn fill(&mut self, fill: Option<char>) -> &mut Self;
pub fn alignment(&mut self, alignment: Option<Alignment>) -> &mut Self;
pub fn width(&mut self, width: Option<usize>) -> &mut Self;
pub fn precision(&mut self, precision: Option<usize>) -> &mut Self;
pub fn get_sign(&self) -> Option<Sign>;
pub fn get_zero_pad(&self) -> bool;
pub fn get_alternate(&self) -> bool;
pub fn get_fill(&self) -> Option<char>;
pub fn get_alignment(&self) -> Option<Alignment>;
pub fn get_width(&self) -> Option<usize>;
pub fn get_precision(&self) -> Option<usize>;
pub fn create_formatter<'a>(self, write: &'a mut (dyn Write + 'a)) -> Formatter<'a>;
pub fn modified_formatter_from<'a>(self, write: &mut Formatter<'a>) -> Formatter<'a>;
}
impl<'a> Formatter<'a> {
pub fn new(write: &'a mut (dyn Write + 'a), options: FormattingOptions) -> Self;
pub fn with_options(&mut self, options: FormattingOptions) -> Self;
pub fn sign(&self) -> Option<Sign>;
pub fn options(&self) -> FormattingOptions;
} Anything I forgot? Or any further feedback? |
I think it's better to leave out Other than that, this looks good to me. :) |
Thanks. After thinking a bit more about this, I believe you are right – the practical use for this method is probably too small to justify the added complexity (and the difficulty in finding a good name for it). If there is indeed any real need for this, another ACP should probably be opened for that. I'll hopefully find time this weekend to have a go at implementing this. Until then, should this ACP issue stay open? I'm not familiar with the process and the std-dev-guide isn't clear on that, but a search through past issues seems to indicate that ACPs are normally closed once accepted. |
I would like to see this approved. I came up with a solution almost exactly like this and posted it on the Rust Zulip, and you can see my motivation for it there. Basically, having this interface would simplify an over 40-line, repetitive match statement (which doesn't even cover all cases, and requires some weird handling for custom fill characters) into a simple 20-or-so line, intuitive segment of code which simply builds a |
Edit 2023-10-23: Added a bit of discussion about why a
new
function onFormatter
instead of the builder pattern is problematicFollow-up to #280.
Proposal
Problem statement
Creating a custom
Formatter
at runtime as well as modifying an existingFormatter
is currently not supported, one can only use theFormatter
s passed in by the formatting macros. Being able to create and modify them could be useful for multiple use cases:Motivating examples or use cases
fmt_internals
feature to build a customstd::fmt::Arguments
which has theFormatter
baked in (or rather the mostly equivalentfmt::rt::Placeholder
struct), see here. In consequence, this crate requires nightly Rust. (Note that the interface isn't the current one as runtime-fmt hasn't been updated since 2019)generate_code!
, it generates a function containing aformat_args!
call for every combination of alignment, signedness, default/alternative representation, zero/space padding, width (via variable), precision (via variable) and formatting trait for a total of 1024format_args!
invocations at the time of writing. Fill characters are not supported, as those cannot be passed via a variable to theformat_args!
call but must be part of the format specifier. (If you are interested to see the result of this approach, runcargo expand
in the crate root and search for a huge function namedformat_value
)Solution sketch
In
std::fmt
:Note that all
FormatterBuilder
methods take and returnself
by value. This is a bit unergonomic, but necessary:build
must takeself
by value in order to transfer ownership of theWrite
trait object to the returnedFormatter
.Alternatives
new
function. If in the future,Formatter
gets refactored to be generic struct with the output struct used as a generic type parameter,fn new
will not match this new interface. However,Formatter
in this fashion is possible anyway – this would change the interface and therefore would be a breaking change.Formatter
s instead of creating new ones is problematic, see [ACP] Provide an interface for creating and modifying instances of fmt::Formatter #280 (comment)new
function onFormatter
instead of the builder pattern is problematic if formatting specifiers get additional parameters added someday (to my knowledge, there aren't any plans, but I believe it is at least within the realms of possibility). Also, this method would take 9 parameters – it wouldn't be very clear to the reader of the code what is happening therenew_from_formatter
seems to be superfluous as it is equivalent toFormatterBuilder::new(fmt).with_formatting_from(fmt)
. However, there are two reasons this function is necessary (or at least very nice to have):new_from_formatter
can set the underlyingWrite
trait object to theWrite
trait object contained infmt
instead of theWrite
impl offmt
itself, thereby avoiding one lookup in the virtual method table (though rustc may be able to optimize this out anyway)new
takeswrite
as a mutable reference, so the borrow checker wouldn't allow callingwith_formatting_from
on the mutably borrowedfmt
.Links and related work
Relevant previous libs team discussion: rust-lang/rfcs#3394 (comment)
Previous ACP with a different approach: #280
What happens now?
This issue contains an API change proposal (or ACP) and is part of the libs-api team feature lifecycle. Once this issue is filed, the libs-api team will review open proposals as capability becomes available. Current response times do not have a clear estimate, but may be up to several months.
Possible responses
The libs team may respond in various different ways. First, the team will consider the problem (this doesn't require any concrete solution or alternatives to have been proposed):
Second, if there's a concrete solution:
The text was updated successfully, but these errors were encountered: