How do I represent an Optional String in Go?

A logical solution would be to use *string as mentioned by Ainar-G. This other answer details the possibilities of obtaining a pointer to a value (int64 but the same works for string too). A wrapper is another solution.

Using just string

An optional string means a string plus 1 specific value (or state) saying "not a string" (but a null).

This 1 specific value can be stored (signaled) in another variable (e.g bool) and you can pack the string and the bool into a struct and we arrived to the wrapper, but this doesn't fit into the case of "using just a string" (but is still a viable solution).

If we want to stick to just a string, we can take out 1 specific value from the possible values of a string type (which has "infinity" possible values as the length is not limited (or maybe it is as it must be an int but that's all right)), and we can name this specific value the null value, the value which means "not a string".

The most convenient value for indicating null is the zero value of string, which is the empty string: "". Designating this the null element has the convenience that whenever you create a string variable without explicitly specifying the initial value, it will be initialized with "". Also when querying an element from a map whose value is string will also yield "" if the key is not in the map.

This solution suits many real-life use-cases. If the optional string is supposed to be a person's name for example, an empty string does not really mean a valid person name, so you shouldn't allow that in the first place.

There might be cases of course when the empty string does represent a valid value of a variable of string type. For these use-cases we can choose another value.

In Go, a string is in effect a read-only slice of bytes. See blog post Strings, bytes, runes and characters in Go which explains this in details.

So a string is a byte slice, which is the UTF-8 encoded bytes in case of a valid text. Assuming you want to store a valid text in your optional string (if you wouldn't, then you can just use a []byte instead which can have a nil value), you can choose a string value which represents an invalid UTF-8 byte sequence and thus you won't even have to make a compromise to exclude a valid text from the possible values. The shortest invalid UTF-8 byte sequence is 1 byte only, for example 0xff (there are more). Note: you can use the utf8.ValidString() function to tell if a string value is a valid text (valid UTF-8 encoded byte sequence).

You can make this exceptional value a const:

const Null = "\xff"

Being this short also means it will be very fast to check if a string equals to this.
And by this convention you already have an optional string which also allows the empty string.

Try it on the Go Playground.

const Null = "\xff"

func main() {
    fmt.Println(utf8.ValidString(Null)) // false

    s := Null
    fmt.Println([]byte(s)) // [255]
    fmt.Println(s == Null) // true
    s = "notnull"
    fmt.Println(s == Null) // false
}

You could use something like sql.NullString, but I personally would stick to *string. As for awkwardness, it's true that you can't just sp := &"foo" unfortunately. But there is a workaround for this:

func strPtr(s string) *string {
    return &s
}

Calls to strPtr("foo") should be inlined, so it's effectively &"foo".

Another possibility is to use new:

sp := new(string)
*sp = "foo"