17

I'm used to Java's String where we can pass null rather than "" for special meanings, such as use a default value.

In Go, string is a primitive type, so I cannot pass nil (null) to a parameter that requires a string.

I could write the function using pointer type, like this:

func f(s *string)

so caller can call that function either as

f(nil)

or

// not so elegant
temp := "hello";
f(&temp) 

but the following is unfortunately not allowed:

// elegant but disallowed
f(&"hello");

What is the best way to have a parameter that receives either a string or nil?

2
  • 1
    I believe you need to let go of Java, and get used to Go way instead. Why do you need to pass a nil instead of a string? what do you want to achieve? ps. you can also use fmt.Printf(*new(string)) - which is 'zero' for the type string Commented Aug 17, 2012 at 14:18
  • 1
    @ŁukaszGruner How is it a Java thing? “Why do you need to pass a nil instead of a string?” Because I need to not pass any string, including ""! Commented May 2, 2021 at 9:54

5 Answers 5

4

Warning: the following is pre-Go1 code. That is, it's from a pre-release version and is not valid Go code.

I thought some more about how I would implement this using a struct. Here's what I came up with:

type MyString struct {
    val string;
}

func f(s MyString) {
    if s == nil {
        s = MyString{"some default"};
    }
    //do something with s.val
}

Then you can call f like this:

f(nil);
f(MyString{"not a default"});
Sign up to request clarification or add additional context in comments.

Comments

3

If you need to handle a possibble null value (becasue you are talking to a database that may provide them for example) the database/sql package has types such as sql.NullString and sql.NullInt64 that allow you to test if you have been provided with a value or not using their .Valid field.

Comments

2

You can declare an interface to restrict the type to string, and since interfaces also accept nil, you'd have both cases covered. This is how you can implement it:

type (
    // An interface which accepts a string or a nil value.
    //
    // You can pass StrVal("text") or nil.
    StrOrNil interface{ isStrOrNil() }

    StrVal string // A string value for StrOrNil interface.
)

func (StrVal) isStrOrNil() {} // implement the interface

And this is how you use it:

func Foo(name StrOrNil) {
    switch nameVal := name.(type) {
    case StrVal:
        fmt.Printf("String value! %s\n", string(nameVal))
    default:
        fmt.Println("Null value!")
    }
}

func main() {
    Foo(StrVal("hello world"))
    Foo(nil)
}

Test it in the playground.

3 Comments

What is the performance impact of this? Would you provide further information on e.g. heap allocations?
@SOFe There is no heap allocation involved, other than the creation of the string itself. StrVal is a type alias (zero cost), and StrOrNil is an interface to restrict the type.
Wrapping a non-pointer value in an interface usually results in a heap allocation. A string is a struct that contains an int that is the length of the string and a pointer to the array that contains the characters of the string. Interfaces can only hold pointers so when you put a string in an interface it allocates the string struct on the heap and puts a pointer to this string in the interface. That means that you may end up with two pointers, something like interface{&stringStruct{&charArray{}}}
1

Not realy attend answer : but warping value in a structure can provide some generic utility methode. (Haskell Maybe ?)

//#maybe.go
package maybe

import "log"

type MayHaveValue struct {
 IsValue bool;
}

func (this MayHaveValue) IsJust() bool {
 return this.IsValue
}

type AString struct {
 MayHaveValue;
 Value string;
}

func String(aString string) AString {
 return AString{MayHaveValue{true}, aString}
}

var NoString AString = AString{MayHaveValue{false}, ""}

func (this AString) String() (value string) {
 if this.IsJust() == true {
  value = this.Value;
 } else {
  log.Crash("Access to non existent maybeString value");
 }
 return;
}

func (this AString) OrDefault(defaultString string) (value string) {
 if this.IsJust() {
  value = this.Value;
 } else {
  value = defaultString;
 }
 return;
}

//#main.go
package main

import "fmt"
import "maybe"

func say(canBeString maybe.AString) {
 if canBeString.IsJust() {
  fmt.Printf("Say : %v\n", canBeString.String());
 } else {
  fmt.Print("Nothing to say !\n");
 }
}

func sayMaybeNothing (canBeString maybe.AString) {
 fmt.Printf("Say : %v\n", canBeString.OrDefault("nothing"));
}

func main() {
 aString := maybe.String("hello");
 say(aString);
 sayMaybeNothing(aString);
 noString := maybe.NoString;
 say(noString);
 sayMaybeNothing(noString);
}

Comments

-3

Loose the Java-think and just pass f(""). Then test using len():

func f(str string) { if len(str) > 0 { ... } else { ... } }

Either the string is empty and has semantic meaning of you nil case, or else has some string data to process. Can't see the problem with that.

1 Comment

The problem is that you may have null strings as ordinary parameters, not meaning "the default". What you suggest is "in-band signaling" (like using 0 or -1 for an integer) and is widely seen as a bad idea because you may need the "special" value for an ordinary meaning.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.