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
  fmt.Fprintf(b, "<h1>Hello, %s!</h1>", name)
	return b.String()

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

Share this

Related Content

Empty structs

We finally we have enough knowledge for the EBNF format not to seem completely foreign, so let’s jump back and take a look at that, with the examples provided in the spec… Struct types … StructType = "struct" "{" { FieldDecl ";" } "}" . FieldDecl = (IdentifierList Type | EmbeddedField) [ Tag ] . EmbeddedField = [ "*" ] TypeName [ TypeArgs ] . Tag = string_lit . // An empty struct.

Struct tags

Struct types … A field declaration may be followed by an optional string literal tag, which becomes an attribute for all the fields in the corresponding field declaration. An empty tag string is equivalent to an absent tag. The tags are made visible through a reflection interface and take part in type identity for structs but are otherwise ignored. struct { x, y float64 "" // an empty tag string is like an absent tag name string "any string is permitted as a tag" _ [4]byte "ceci n'est pas un champ de structure" } // A struct corresponding to a TimeStamp protocol buffer.

Struct method promotion

Yesterday we saw an example of struct field promotion. But methods (which we haven’t really discussed yet) can also be promoted. Struct types … Given a struct type S and a named type T, promoted methods are included in the method set of the struct as follows: If S contains an embedded field T, the method sets of S and *S both include promoted methods with receiver T. The method set of *S also includes promoted methods with receiver *T.