-
Notifications
You must be signed in to change notification settings - Fork 18k
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
proposal: fmt: add Formatter fallback #51195
Comments
CC @robpike |
Formatter a rarely used interface so I'm not sure if this is a problem widespread enough to need a response. It can be a little annoying to implement, I admit. I'd call it Default rather than Fallback, but that's a small point. Also it might be cleaner both in implementation and use to provide instead a wrapper type that shields the magic formatting, making it easy recover the default behavior for any verb. That would also provide a solution to the endless issue of String methods accidentally invoking themselves. I still come back my original point though: Is this a significant enough problem to warrant a general solution? |
@robpike I also prefer
I've been writing Go for over 8 years and still frequently do this :)
To answer your question: although The default verbs and flags are very useful and it's annoying when they aren't implemented. For example, a common use of Here is a random example from our code base. It really only needs to implement two cases: type SomeError []T
func (e SomeError) Format(f fmt.State, c rune) {
switch {
case c != 'v':
fmt.Fprintf(f, makeFormat(f, c), ([]T)(e))
case !f.Flag('+'):
fmt.Fprintf(f, makeFormat(f, 's'), e.Error())
default:
[...]
}
}
func makeFormat(f fmt.State, c rune) string {
var b strings.Builder
b.WriteByte('%')
for _, c := range "+-# 0" {
if f.Flag(int(c)) {
b.WriteRune(c)
}
}
if width, ok := f.Width(); ok {
b.WriteString(strconv.Itoa(width))
}
if prec, ok := f.Precision(); ok {
b.WriteByte('.')
b.WriteString(strconv.Itoa(prec))
}
b.WriteRune(c)
return b.String()
} |
Also, I agree with Go's philosophy of only reluctantly adding new APIs to the standard library, especially to |
The original design should perhaps have had a return value to indicate that the default behavior should apply. Too late for that now, although we could add a new interface. |
Maybe fmt.DefaultFormat to make clear it's for use by Format? Otherwise, this seems OK. |
This proposal has been added to the active column of the proposals project |
I think this one needs a hard think. There are multiple solutions with different complexities, advantages, and tradeoffs. |
This is a minor problem in a minor, little-used feature. A simple answer should be sufficient. A suggestion. In the
When a This would be easy to do and would also provide a clean mechanism for the recursive |
@robpike I like the simplicity. I have a question. What should this program print? package main
import "fmt"
func main() {
x := Uint128{1, 2}
fmt.Printf("%#v\n", x)
}
type Uint128 struct {
lo, hi uint64
}
func (x Uint128) Format(s fmt.State, v rune) {
switch v {
case 's', 'd':
// print base 10
case 'x', 'X:
// print base 16
default:
fmt.Fprint(s, fmt.Default{Value: x})
}
} Or, more directly: how will the |
I suppose As I said above, much thinking is required. |
@robpike I agree this needs a lot of thought. It seems to me the original fmt.Formatter is rather difficult to use. We could probably think of a better design. |
I've also avoided writing custom formatters particularly because there's no great way to override one or two verbs without reimplementing the whole thing, and fmt.State has no trivial way to reconstruct fmt.Formatter inputs into the original verb, in order to pass it, alongside a conversion of the receiver to a derived type, to fmt.Fprintf. Perhaps we could just document that when fmt.State receives no writes, it'll just re-interpret the value as though it were not a fmt.Formatter, and proceed accordingly (thus the "fallback" would merely be returning from the Format method without the fmt.State.Write method having been called). |
We could also implement e.g. golang.org/x/tools/fmt/fmtutil.Fallback, write about it, measure adoption, and then make a decision from there. |
See #51668. |
Putting this on hold until whether we see whether the solution to #51668 (which is suggested implicitly in the original comment here, one is surprised to see) solves the problem well enough. It well might. |
Placed on hold. |
Change https://go.dev/cl/400875 mentions this issue: |
Sometimes when implementing a Formatter it's helpful to use the fmt package without invoking the formatter. This new function, FormatString, makes that easier in some cases by recreating the original formatting directive (such as "%3.2f") that caused Formatter.Format to be called. The original Formatter interface is probably not what we would design today, but we're stuck with it. FormatString, although it takes a State as an argument, compensates by making Formatter a little more flexible. The State does not include the verb so (unlike in the issue), we must provide it explicitly in the call to FormatString. Doing it there minimizes allocations by returning the complete format string. Fixes #51668 Updates #51195 Change-Id: Ie31c8256515864b2f460df45fbd231286b8b7a28 Reviewed-on: https://go-review.googlesource.com/c/go/+/400875 Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org> Reviewed-by: Russ Cox <rsc@golang.org> TryBot-Result: Gopher Robot <gobot@golang.org> Reviewed-by: Dmitri Shuralyov <dmitshur@google.com> Run-TryBot: Russ Cox <rsc@golang.org>
Sometimes when implementing a Formatter it's helpful to use the fmt package without invoking the formatter. This new function, FormatString, makes that easier in some cases by recreating the original formatting directive (such as "%3.2f") that caused Formatter.Format to be called. The original Formatter interface is probably not what we would design today, but we're stuck with it. FormatString, although it takes a State as an argument, compensates by making Formatter a little more flexible. The State does not include the verb so (unlike in the issue), we must provide it explicitly in the call to FormatString. Doing it there minimizes allocations by returning the complete format string. Fixes golang#51668 Updates golang#51195 Change-Id: Ie31c8256515864b2f460df45fbd231286b8b7a28 Reviewed-on: https://go-review.googlesource.com/c/go/+/400875 Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org> Reviewed-by: Russ Cox <rsc@golang.org> TryBot-Result: Gopher Robot <gobot@golang.org> Reviewed-by: Dmitri Shuralyov <dmitshur@google.com> Run-TryBot: Russ Cox <rsc@golang.org>
Just to follow up, since I found this searching for a solution to the same problem, the addition of func (t T) Format(f fmt.State, c rune) {
switch c {
case 'd':
fmt.Fprint(f, "Ceci n'est pas un nombre")
case 's':
fmt.Fprint(f, t.String())
default:
type hideMethods T
type T hideMethods
fmt.Fprintf(f, fmt.FormatString(f, c), T(t))
}
} https://go.dev/play/p/BkqOaS4CL68 This can be simplified further if the underlying type of |
Thanks for the report, @dnesting. Maybe this can be closed now. |
This proposal has been added to the active column of the proposals project |
No change in consensus, so declined. |
Implementing
fmt.Formatter
overrides all other formatting. This makes it difficult to selectively implement verbs: you must either implement every verb and flag combination (including width, etc.) or leave them unimplemented.For example, I have a
UUID
type. ItsString
method defaults to the 36-byte encoding with hyphens. I also want to add support for%x
so users can encode it as a 32-byte hexadecimal string. BecauseUUID
implementsfmt.Stringer
, I have to add%x
support viafmt.Formatter
(otherwise%x
will incorrectly encode the 36-byte encoding as hexadecimal). But this means I lose%q
,%X
,%v
, etc. support. If I want those, I have to implement them myself. See https://go.dev/play/p/lb8mkyhN3-LIdeally, the
fmt
package would allow me to write something likeAs another example, I wrote this code (which admittedly I am not very proud of, but hey) to work around the same issue: https://github.com/ericlagergren/decimal/blob/aca2edc11f73e28a0e93d592cc6c3de4a352a81c/big.go#L895
Proposal Template
Would you consider yourself a novice, intermediate, or experienced Go programmer?
Experienced (have been writing Go since 1.3 or so).
What other languages do you have experience with?
Varying degrees of familiarity with C, Java, Python, JavaScript, Bash, Assembly, etc.
Would this change make Go easier or harder to learn, and why?
N/A
Has this idea, or one like it, been proposed before?
Did not immediately find anything.
Who does this proposal help, and why?
Me and other users of the
fmt
package.What is the proposed change?
Add a mechanism to the
fmt
package to allowFormatter
implementations to "fall back" to the default formatting. For example, an API similar toThe specific API is mostly unimportant to me.
Is this change backward compatible?
Yep.
Show example code before and after the change.
See above.
What is the cost of this proposal? (Every language change has a cost).
Very likely none.
N/A
Depends on how it's implemented.
Can you describe a possible implementation?
See above.
How would the language spec change?
N/A
Orthogonality: how does this change interact or overlap with existing features?
It augments
fmt.Formatter
. It does not duplicate any existing functionality.Is the goal of this change a performance improvement?
Nope.
Does this affect error handling?
Nope.
Is this about generics?
Nope.
The text was updated successfully, but these errors were encountered: