Immutable strings

February 23, 2023

String Types

A string type represents the set of string values. A string value is a (possibly empty) sequence of bytes. The number of bytes is called the length of the string and is never negative. Strings are immutable: once created, it is impossible to change the contents of a string. The predeclared string type is string; it is a defined type.

Anyone who’s ever written even just “Hello, World” in virtually any language has had experience with strings. So there’s not a lot to say about the concept here.

What makes strings in Go stand out, though, is that they are immutable.

Although the langauge does a pretty good job of hiding that detail from us, the results can be a bit surprising in a few cases.

In short, once a string is created, it cannot be changed. That sounds simple enough. But we see code like this all the time:

str := "Hello, World"  // Create a string
// Then later...
str = "Howdy, Pardner" // Apparently "changing" the string... what gives?

This conflict, which may appear to conflict with the spec, doesn’t actually. But what’s happening is subtle.

The above code actually creates two distinct strings, but assigns them, one at a time, to a variable of type string, called str.

Eh, what?

Line one creates a variable, str, and also creates a string in memory with the bytes that make up “Hello, World”, and sets the str variable to point to that memory location.

Then on line three, we create a new, immutable string in memory, this time with the bytes that make up “Howdy, Pardner”, then we update the str variable to point to that new memory location. The first string is then eligible for garbage collection (assuming it’s not referenced elsewhere).

Where this often bites people is when doing string concatentation. Especially if you come from a language like Perl or JavaScript, where code like this is fairly common:

func generateBody(name string ) string {
	var body string
	body += "<html>"
	body += "<body>"
	body += fmt.Sprintf("<h1>Hello, %s!</h1>", name)
	body += "</body>"
	body += "<html>"
	return body
}

This seemlingly innocent code actually creates 6 distinct strings in memory, each a bit longer than the previous, then discards the first 5 via garbage collection, and returns only the 6th.

The strings.Builder function helps us work around the quirk of the language, by building strings gradually in a container type that doesn’t do any new memory allocations until the final string is requested.

func generateBody(name string ) string {
  var b strings.Builder
  b.WriteString("<html>")
  b.WriteString("<body>")
  fmt.Fprintf(b, "<h1>Hello, %s!</h1>", name)
  b.WriteString("</body>")
  b.WriteString("</html>")
	return b.String()
}

Quotes from The Go Programming Language Specification, Version of January 19, 2023


Share this

Direct to your inbox, daily. I respect your privacy .

Unsure? Browse the archive .

Related Content


unsafe.String and unsafe.StringData

Package unsafe … The function String returns a string value whose underlying bytes start at ptr and whose length is len. The same requirements apply to the ptr and len argument as in the function Slice. If len is zero, the result is the empty string "". Since Go strings are immutable, the bytes passed to String must not be modified afterwards. [Go 1.20] The function StringData returns a pointer to the underlying bytes of the str argument.


Evaluation of range expressions

For statements with range clause … The range expression x is evaluated once before beginning the loop, with one exception: if at most one iteration variable is present and len(x) is constant, the range expression is not evaluated. The first part of this seems pretty self-evident, as it follows the same pattern as a for statement with a for clause: The expression needs to be evaluated once before the loop executes.


Converting rune slices to strings

Yesterday we started through the list of rules and special cases for converting to and from string types. Let’s continue… Conversions to and from a string type … Converting a slice of runes to a string type yields a string that is the concatenation of the individual rune values converted to strings. string([]rune{0x767d, 0x9d6c, 0x7fd4}) // "\u767d\u9d6c\u7fd4" == "白鵬翔" string([]rune{}) // "" string([]rune(nil)) // "" type runes []rune string(runes{0x767d, 0x9d6c, 0x7fd4}) // "\u767d\u9d6c\u7fd4" == "白鵬翔" type myRune rune string([]myRune{0x266b, 0x266c}) // "\u266b\u266c" == "♫♬" myString([]myRune{0x1f30e}) // "\U0001f30e" == "🌎" I think this one is pretty intuitive.

Get daily content like this in your inbox!

Subscribe