I've encountered some go code that appears to use %e for formatting an error for display to the screen. A simplified version would be code like this
err := errors.New("La de da")
fmt.Printf("%e\n", err)
outputs
&{%!e(string=La de da)}
However, if I look at the go manual, it says %e is for formatting floating point numbers in scientific notation. That output doesn't look like scientific notation, so I'm wondering
If this is a specific notation, what is it? (i.e. is there a %. formatting option I could use to get that format)
If it's not a specific notation, what weird thing is going on under the hood that leads to an error being rendered in this way?
What silly, obvious thing am I missing that renders most of what I've said in this post wrong?
Read the Go documentation.
Package fmt
Printing
Format errors:
If an invalid argument is given for a verb, such as providing a string
to %d, the generated string will contain a description of the problem,
as in these examples:
Wrong type or unknown verb: %!verb(type=value)
Printf("%d", hi): %!d(string=hi)
Too many arguments: %!(EXTRA type=value)
Printf("hi", "guys"): hi%!(EXTRA string=guys)
Too few arguments: %!verb(MISSING)
Printf("hi%d"): hi%!d(MISSING)
Non-int for width or precision: %!(BADWIDTH) or %!(BADPREC)
Printf("%*s", 4.5, "hi"): %!(BADWIDTH)hi
Printf("%.*s", 4.5, "hi"): %!(BADPREC)hi
Invalid or invalid use of argument index: %!(BADINDEX)
Printf("%*[2]d", 7): %!d(BADINDEX)
Printf("%.[2]d", 7): %!d(BADINDEX)
All errors begin with the string "%!" followed sometimes by a single
character (the verb) and end with a parenthesized description.
For your example,
package main
import (
"errors"
"fmt"
)
func main() {
err := errors.New("La de da")
fmt.Printf("%e\n", err)
}
Playground: https://play.golang.org/p/NKC6WWePyxM
Output:
&{%!e(string=La de da)}
Documentation:
All errors begin with the string "%!" followed sometimes by a single
character (the verb) and end with a parenthesized description.
Wrong type or unknown verb: %!verb(type=value)
Printf("%d", hi): %!d(string=hi)
When formatting errors into strings using fmt.Printf()/Println(), you can do the following:
err := fnThatReturnsErr()
if err != nil {
fmt.Println("The error is %v", err)
}
I believe %v is the formatting option you were looking for.
Related
Calls to string formatting functions like fmt.Printf seem to be a weak spot for the Go compiler. I end up with lots of bugs (using the wrong formatter after a refactor, forgetting to include all the arguments) that only reveal themselves at runtime. So I end up having to squint hard every time I write one of these.
I did some research today and discovered go tool vet, which appears to work for fmt.Printf, but it doesn't catch mistakes in errors.Errorf (see below).
import "github.com/pkg/errors"
func ReturnError(s string, i int) error {
// Swap %d and %s, and forget to include the second argument
return errors.Errorf("invalid arguments %d and %s", s)
}
Is there an analog to go tool vet that can capture string formatting errors in errors.Errorf()? Also, for my own understanding, why is this such a hard problem? It doesn't seem any more difficult for the Go compiler to catch string formatting type errors than any other kind of type errors.
You can tell go vet which functions to check for (compare godoc.org/cmd/vet):
$ cat x.go
package main
import "github.com/pkg/errors"
func ReturnError(s string, i int) error {
// Swap %d and %s, and forget to include the second argument
return errors.Errorf("invalid arguments %d and %s", s)
}
$ go vet x.go
$ go vet -printfuncs Errorf x.go
# command-line-arguments
./x.go:7: Errorf format %d has arg s of wrong type string
It's not straightforward to do this better for a number of reasons:
Format strings are run-time values: You might well call fmt.Sprintf(prefix + "%s", ...). So it is impossible to catch all invalid format strings at compile time.
Format strings don't have a special type. Hence the compiler can't easily determine that some function (errors.Errorf in this case) expects its arguments to behave like fmt.Printf just by looking at the function definition.
I'm currently trying to take a time.Time object and go and produce a formatted string that happens to include some numbers that I do NOT want to be parsed as a time. For example, consider the following program:
package main
import (
"fmt"
"time"
)
func main() {
now := time.Now()
msg := now.Format("Encountered a 502 error on 2006-01-02 15:02 MST")
fmt.Println(msg)
}
Unfortunately, the text "502" is interpreted as a time here: running this code will produce output like Encountered a 1112 error on 2018-07-12 9:12 UTC.
Is there any way to escape the 502 numbers so they aren't interpreted as numbers? E.g. similar to how you can escape the % meta-character by using %% in languages that implement strftime-style formatting logic?
Or is my only option to just split this up and use two formatting operations instead of one?
nowString := now.Format("2006-01-02 15:02 MST")
msg := fmt.Sprintf("Encountered 502 error on %s", nowString)
No, there is no escape for numbers in time.Format. The purpose of that method is for formatting time, not for formatting strings in general.
If this is used from multiple locations, the usual solution would be to make a simple function to do the formatting
func nowMessage(msg string) string {
const layout = "2006-01-02 15:02 MST"
return fmt.Sprintf("%s %s", msg, time.Now().Format(layout))
}
Consider,
package main
import "fmt"
func main() {
name := "johnny"
fmt.Println("Hello world %s\n", name)
}
prints out,
Hello world %s
johnny
Why do I get the %s instead of this,
package main
import "fmt"
func main() {
name := "johnny"
fmt.Printf("Hello world %s\n", name)
}
which prints Hello world johnny?
I have tried to figure out the answer from the documentation,
If the format (which is implicitly %v for Println etc.) is valid for a
string (%s %q %v %x %X), the following two rules apply:
If an operand implements the error interface, the Error method will be invoked to convert the object to a string, which will then be
formatted as required by the verb (if any).
If an operand implements method String() string, that method will be invoked to convert the object to a string, which will then be
formatted as required by the verb (if any).
But I'm having trouble understanding if this is affecting my program.
the f in Printf is for "Formatting." That's why the %? verbs do anything at all, because the function is built to parse for them. Println does no such formatting.
Formatting isn't a property of strings like in some languages (maybe you, like myself, came from Python?)
Println just prints the string and appends a newline to it. Printf is short for 'print format' and is based off the C library which is where the conventions for format specifiers ect come from.
Simple answer is it's as designed. If you want to use format specifiers you gotta call the format method.
I am trying to use sscanf to parse multiple string fields. Here is an example code snippet:
package main
import "fmt"
func main() {
var name, currency string
_, err := fmt.Sscanf("transaction benson: dollars", "transaction %s: %s", &name, ¤cy)
fmt.Println(err, name, currency)
}
The output is
input does not match format benson:
Program exited.
%s is greedy and gobbles up to the next space, which means it eats up the colon. After processing the %s, it then tries to scan in the colon, but wait, that’s already been consumed, and the next character is actually a space, not a colon! So it fails.
In C you’d get around this by using %[^:] rather than %s, but it appears Go doesn’t support this. You might need to find some way to parse your string without Sscanf.
The following code
package main
import (
"fmt"
)
func main() {
fmt.Println(say(9))
}
func say(num int)(total string){
return fmt.Sprintf("There are %s reasons to code!", num)
}
Produces the following output
There are %!s(int=9) reasons to code!
My question
What should I do to interpolate a number inside a string?
If you want to always use the "default" representation of no matter what type, use %v as in
fmt.Sprintf("There are %v reasons to code!", num)
Try %d instead of %s. The d stands for decimal.
The appropriate documentation is here:
http://golang.org/pkg/fmt/
The output is saying exactly what is happening and what you need to know!
As you are trying to use a %s verb which is meant to strings the output says that:
!s(int=0) which means:
The value is not a string, but an integer.
Thus, if you want to know what to use instead take a look at the fmt package page https://golang.org/pkg/fmt/ at the "integers" table:
%b base 2
%c the character represented by the corresponding Unicode code point
%d base 10
%o base 8
%q a single-quoted character literal safely escaped with Go syntax.
%x base 16, with lower-case letters for a-f
%X base 16, with upper-case letters for A-F
%U Unicode format: U+1234; same as "U+%04X"
So you can use any of this verbs to have the output correctly represented.
Or as previous answers says, you can also use the %v verb which means:
"the value in its default format".