Coming from other languages it can be surprising that Go does not support string interpolation.
What do I mean by string interpolation?
Well, take this snippet of PHP for example:
$x = 49;
$msg = "Hello world";
echo "The number is $x and the message is $msg";
When running the program PHP will evaluate the double quoted string and automatically replace the $x
and $msg
placeholders with the appropriate values. In the end, The number is 49 and the message is Hello world
will be echoed.
This evaluation and replacement is called string interpolation. You’re essentially providing mini-templates in which you can directly refer to variables.
As said before, Go does not support string interpolation.
In this article we’ll take a look at alternatives and we’ll discuss why Go doesn’t support string interpolation.
Formatting
You’ve probably seen the fmt
package before if you’ve done a “Hello world” style tutorial.
fmt
stands for “format” or “formatting”. This package provides a lot of functionality for dealing with formatted text.
fmt.Sprint
If you want a syntax that is superficially similar to string interpolation, you should take a look at fmt.Sprint
.
This function concatenates all provided arguments into a single string
.
- Arguments are combined in the order they are provided.
- Will use default format for each type unless the type implements the
fmt.Stringer
interface, in which casefmt.Sprint
will call theString
method. - Spaces are inserted between non-string arguments.
If we recreate our earlier example in Go, we get the following code.
package main
import (
"fmt"
)
func main() {
x := 49
msg := "Hello world"
out := fmt.Sprint("The number is ", x, " and the message is ", msg)
fmt.Println(out)
}
Alternatively, if you immediately want to print the string to standard output you can use fmt.Print
instead of fmt.Sprint
.
This pattern applies generally in the fmt
package, every function prefixed with “S” returns a string and has a version without the prefix that prints the string to standard output instead.
fmt.Sprintf
fmt.Sprintf
also returns a single string
but it uses a layout for formatting instead of concatenating the arguments.
This layout uses “verbs” that will replaced by provided values. Some common verbs are:
%v
for values in their default format.%d
base 10 integers.%b
base 2 integers.%f
decimal points but no exponents.%s
string or bytes.%q
quoted string or bytes.
Some verbs allow for formatting options. You can find the full list here.
If we again recreate out earlier example, it looks like this:
package main
import (
"fmt"
)
func main() {
x := 49
msg := "Hello world"
out := fmt.Sprintf("The number is %d and the message is %s", x, msg)
fmt.Println(out)
}
In my view there are two downsides to fmt.Sprintf
:
- Order of verbs in the layout and function arguments are coupled. This can lead to issues when you have several layouts with verbs in different positions.
- Formatting options can be quite cryptic. I often need to check the documentation.
Templating
When you’re dealing with larger pieces of text where you want to refer to variables by name, re-use templates or define custom formatting rules.
If you’re dealing with large pieces of text and want to:
- Refer to variables by name.
- Re-use similar parts of text.
- Define custom formatting rules or functions.
You’re probably in need of a full templating system.
The Go standard library contains two templating packages: text/template
for plain text and html/template
for HTML templates.
The reason for this split is that the HTML package has extra functionality to secure HTML output against specific attacks.
Below we use the text/template
package to recreate our earlier example:
package main
import (
"log"
"os"
"text/template"
)
func main() {
const tmpl = "The number is {{.Number}} and the message is {{.Message}}"
// create a new template called test and parse the above text template.
t, err := template.New("test").Parse(tmpl)
if err != nil {
log.Fatal(err)
}
type Data struct {
Number int
Message string
}
err = t.Execute(os.Stdout, Data{
Number: 49,
Message: "Hello World",
})
if err != nil {
log.Fatal(err)
}
}
As you can see, the text/template
package takes a two-step approach to creating templates:
- Create the textual template(s) in memory (
t
in this case). This needs to be done only once for each template after which it can be re-used. - Execute the template. This will insert the provided values into the template and write the result to the provided
io.Writer
.
The html/template
package works in a similar way.
Concatenation
To concatenate strings together you can also use the +
or +=
operators. This only works if all involved values are strings.
See the example below.
package main
import (
"fmt"
)
func main() {
x := 49
xString := fmt.Sprint(x)
msg := "Hello world"
out := "The number is " + xString + " and the message is " + msg
fmt.Println(out)
}
Here you can see that we first need to convert the integer x
to a string before we can concatenate it.
Why no string interpolation?
There have been several proposals to add string interpolation to Go, see:
- add simple string interpolation similar to Swift.
- Go 2: string interpolation evaluating to string and list of expressions.
- Go 2: string interpolation.
The main arguments against these prosals seem to be:
- While possibly more readable, for simple cases it’s roughly similar to a
fmt.Sprint
call. - If layout options are supported, it’s not necessarily more readable than a
fmt.Sprintf
call. - It will require “lifting” formatting rules into the Go spec, complicating the spec.
This is related to string interpolation in general, but I personally prefer to keep strings “portable”. String interpolation binds strings to the current scope and context, making it a bit more work to move them around when refactoring.
Anyway, that’s it for this article, let me know if you have any questions or suggestions.
Happy coding!
Get my free newsletter every second week
Used by 500+ developers to boost their Go skills.
"I'll share tips, interesting links and new content. You'll also get a brief guide to time for developers for free."