Go
golang

Goの関数の戻り値にsliceのポインタを返したくないけど、nilの場合もある時

sliceは内部にデータへのポインタを持っており、sliceそのものをポインタで渡すのは冗長であると言われる。他人のコードを見てもsliceのポインタを戻り値で返すケースはあまり見かけない。引数でsliceのポインタを受け取ることはあるけれど。

例としてmapから指定keyの値を[]stringとして返す関数を実装する場合。

func GetArray(mapObject map[string]interface{}, key string) ([]string, error) {
  if mapObject[key] == nil {
     // keyの値がnil、もしくは存在しない場合
     return []string{}, nil
  }
  stringArray, ok := mapObject[key].([]string)
  if ok {
     // 値が[]stringだった場合
     return stringArray, nil
  } else {
     // 値が[]stringでない場合
     return []string{}, errors.New("Invalid value for key. " + key)
  }
}

こうしてしまうと、

  • keyの値がnull、もしくは、keyが存在しない場合
  • keyの値が空配列だった場合

のどちらか区別ができなくなる。前者の場合はerrorを返すこと自体、おかしい気がする。

このようにsliceのポインタを返す方法もある。

func GetArray(mapObject map[string]interface{}, key string) (*[]string, error) {
  if mapObject[key] == nil {
     return nil, nil
  }
  stringArray, ok := mapObject[key].([]string)
  if ok {
     return &stringArray, nil
  } else {
     return nil, errors.New("Invalid value for key. " + key)
  }
}

stringArrayPtr, err := GetArray(mapObject, "key1")
if err != nil {
  // keyの値は[]stringではなかった
} else if stringArrayPtr == nil
  // keyの値はnilだった
} else {
  // keyの値は[]stringだった
  // appendはやfor/rangeは、ポインタではできないので実体を指す必要がある
  *stringArrayPtr = append(*stringArrayPtr, "new string")
  for _, str := range *stringArrayPtr {
    // 何かの処理
  } 
}

この方法なら、keyの値がnilだった時は、nilを返すようになる。しかし、最初に述べたようにsliceのポインタを返すのは冗長であるし、受け手側で実体を参照しないとappendやfor/rangeできないのは混乱をもたらしそうである。

そこでこの案はどうか?

func GetArray(mapObject map[string]interface{}, key string, stringArray *[]string) (bool, error) {
  if mapObject[key] == nil {
     return false, nil
  }
  stringArray2, ok := mapObject[key].([]string)
  if ok {
     *stringArray = stringArray2
     return true, nil
  } else {
     return false, errors.New("Invalid value for key. " + key)
  }
}

var stringArray []stringArray
has, err := GetArray(mapObject, "key1", &stringArray)
if err != nil {
  // keyの値は[]stringではなかった
} else if has {
  // keyの値は[]stringだった
} else {
  // keyの値はnilだった
}

このようにすると、値がnilだった場合とエラーだった場合の区別ができ、かつ、sliceのポインタを返さなくていい。

ちなみにxormというO/Rマッパーライブラリから、この案を拝借しました。