Reference to string literals in Go

20,634

Solution 1

Taking the address of a literal (string, number, etc) is illegal because it has ambiguous semantics.

Are you taking the address of the actual constant? Which would allow the value to be modified (and could lead to a runtime error) or do you want to allocate a new object, copy the constant over and get the address to the new version?

This ambiguity does not exist in the case of test2 since you are dealing with an existing variable of which the semantics are clearly defined. The same would not work if the string was defined as const.

The language spec avoids this ambiguity by explicitly not allowing what you are asking for. The solution is test2. While it is slightly more verbose, it keeps the rules simple and clean.

Of course, every rule has its exceptions, and in Go this concerns composit literals: The following is legal and defined as such in the spec:

func f() interface{} {
    return &struct {
        A int
        B int
    }{1, 2} 
}

Solution 2

For the question of the best solution for your situation of passing around "static" strings,

  1. Pass the string type instead of *string.
  2. Don't make assumptions about what is going on behind the scenes.

It's tempting to give the advice "don't worry about allocating strings" because that's actually true in the case you're describing where the same string is passed around, perhaps many times. It general though, it's really good to think about memory use. It's just really bad to guess, and even worse to guess based on experience with another language.

Here's a modified version of your program. Where do you guess that memory is allocated?

package main

import "fmt"

var konnichiwa = `こんにちは世界`

func test1() *string {
    s := `Hello world`
    return &s
}

func test2() string {
    return `Hej världen`
}

func test3() string {
    return konnichiwa
}

func main() {
    fmt.Println(*test1())
    fmt.Println(test2())
    fmt.Println(test3())
}

Now ask the compiler:

> go tool 6g -S t.go

(I named the program t.go.) Search the output for calls to runtime.new. There's only one! I'll spoil it for you, it's in test1.

So without going off on too much of a tangent, the little look at the compiler output indicates that we avoid allocation by working with the string type rather than *string.

Solution 3

A string in go is immutable and is just a pointer and a length (total length : 2 words).

So you don't have to use a pointer to handle it efficiently.

Just pass the string.

Solution 4

  1. Passing around a string doesn't usually allocate memory in Go - it's a value type (ptr to the bytes and an int len).

  2. Taking an address of a literal is supported only for composite literals like

    v := &T{1, "foo"}

    but not for simple values like

    w := &1

    x := &"foo"

Share:
20,634

Related videos on Youtube

ANisus
Author by

ANisus

Starting with C64 basic in my childhood, going through Atari's STOS, Amiga's AMOS, Motorola 680x0 assembly, C and C++, I now work as a System Developer using a wide range of languages and technologies. However, my passion lies in creating excellent software using Go.

Updated on July 22, 2022

Comments

  • ANisus
    ANisus almost 2 years

    In my application I will frequently pass references to a static string. I wish to avoid having Go allocate memory for each call, but I failed to get the address to my string literal.

    Why is it not possible to take the address of a string literal (see test1() in the example below)? Have I misunderstood the syntax, or is it a limitation due to the internal workings of Go?

    If not possible, what would be the best solution?

    test2() works, but will it allocate memory for the var hej each time?
    test3() will not allocate any new memory, but I wish to avoid clutter outside the function.

    package main
    
    import "fmt"
    
    var konnichiwa = `こんにちは世界`
    
    // Gives the compile error `cannot take the address of "Hello world"`
    func test1() (*string) { return &`Hello world` }
    
    // Works fine
    func test2() (*string) {
        hej := `Hej världen`
        return &hej
    }
    
    func test3() (*string) { return &konnichiwa }
    
    func main(){
        fmt.Println(*test1())
        fmt.Println(*test2())
        fmt.Println(*test3())
    }
    

    Thanks for help!

  • ANisus
    ANisus almost 12 years
    Not only did I get a better understanding when Go actually decides to allocate new memory, but even better (or worse!) you also taught me how use go tool to get the assembly myself. Curses, now I will be sitting analysing Go's assembly code for hours of fun. Thanks! (And yes, I will stick with your solutions)
  • dtoux
    dtoux about 6 years
    What is one need an optional property for a structure with empty string being valid value?
  • psmith
    psmith over 5 years
    I got error go tool: no such tool "6g" using go 1.10 but this worked: go tool compile -S t.go
  • Keith Ripley
    Keith Ripley over 5 years
    This is not helpful as certain APIs require one to pass a pointer to a string.