Recently, I learned that %#q in the fmt package wraps a string in backquotes.
Unlike plain %q, which adds double quotes ", the # flag switches to backquotes `.
This little discovery led me to extend a go-critic check that didn’t know about it.
The discovery
I came across %#q while reviewing a
pull request in Lima
, where I’m a reviewer.
It turns out this isn’t just a cosmetic preference.
The PR solves a real readability problem on Windows, described in
issue #4898
.
Windows file paths use backslashes, and %q escapes each one, so a path renders with doubled backslashes:
msg="failed to resolve vm for \"C:\\Users\\john\\.lima\\default\\lima.yaml\""
With %#q, the path is wrapped in backquotes and no escaping is needed, which is far easier to read:
msg="failed to resolve vm for `C:\Users\john\.lima\default\lima.yaml`"
That’s why the PR replaces %q with %#q in logs and errors so that paths and identifiers are wrapped in backquotes instead of double quotes:
// before
return fmt.Errorf("failed to read meta data file %q: %w", metaDataPath, err)
// after
return fmt.Errorf("failed to read meta data file %#q: %w", metaDataPath, err)
The verbs %q and %#q exist exactly so you don’t have to wrap a string by hand:
- Wrapping a string with
"%s"can be simplified to%q, which also safely escapes special characters. - Wrapping a string with
"`%s`"can be simplified to%#q, which uses raw backtick quoting when the string contains no backtick characters; otherwise it falls back to the%qform.
Double-quoted strings ("...") process escape sequences such as \\ and \n, while backtick strings (`...`) are raw without escaping.
It’s all clearly documented in the
fmt package docs
; I just had never run into the # flag before.
The gap in go-critic
go-critic
is a Go linter that bundles a large collection of checks on style, performance issues, and common errors.
It is supported by
golangci-lint
via the gocritic linter.
go-critic has a
sprintfQuotedString
check that recommends using %q instead of manually wrapping a string with "%s":
fmt.Printf(`"%s"`, v) // suggestion: use %q instead of "%s" for quoted strings
But the check didn’t detect the backquote case. This code passed without any warning:
fmt.Printf("`%s`", v) // not detected, but could be simplified to %#q
So the linter caught one half of the pattern but not the other.
Extending the check
I created a
PR that extends the sprintfQuotedString rule
to also flag manually backquoted strings and suggest %#q.
Now both forms are covered:
fmt.Sprintf(`"%s"`, s)transforms tofmt.Sprintf("%q", s)fmt.Sprintf("`%s`", s)transforms tofmt.Sprintf("%#q", s)
A small change, but it closes a gap and helps everyone use the idiomatic fmt verbs.
The payoff: simpler code
These verbs aren’t just shorter, they let you delete code.
While working on
revive
, I used the same insight to simplify code by replacing a hand-escaped "%s" with %q:
// before
fmt.Sprintf("Import alias \"%s\" is redundant", imp.Name.Name)
// after
fmt.Sprintf("Import alias %q is redundant", imp.Name.Name)
Conclusion
Reading the docs of tools you use every day pays off.
A flag as small as # can simplify your format strings, and a linter is the perfect place to encode that knowledge so the whole community benefits.
By the way, I’m a revive maintainer, a linter expert, and a golangci-lint contributor . If you want to write a custom linter, or set up CI/CD that helps AI write better code, reach out — I’d be happy to help.
