쿼카러버의 기술 블로그

[golang] string pointer는 언제쓸까? 본문

[Golang]

[golang] string pointer는 언제쓸까?

quokkalover 2021. 8. 9. 18:30

Go에서 string은 value다. 따라서, nil이 될 수 없다.  

Go has various value types including strings, integers, floats, booleans, etc.

 

따라서 아래 코드는 컴파일이 될 수 없다. 

x := "I am a string!"

x = nil // Won't compile, strings can't be nil in Go

 

하지만 string을 가리키는 pointer(*string)는 nil이 될 수 있다. 따라서 아래 코드는 컴파일이 가능하다. 

var x *string x = nil

 

그럼 string pointer는 언제쓸까? 일반적으로 golang에서는 nil이 필요하지 않은 상황이면 string pointer는 사용하는 것을 권장하지 않는다. 일단 value가 있는지 없는지를 아래처럼 확인해야 하는 추가 코드가 필요해지기 때문이다.

func UseString(s *string) error {
    if s == nil {
        temp := "" // *string cannot be initialized
        s = &temp // in one statement
    }
    value := *s // safe to dereference the *string
}

즉 ""(empty string)와 nil은 다른 개념으로 이해해야 한다. 

 

 

Use Cases

json, yaml 파일을 struct로 unmarshal(string to golang struct)할 때 아래와 같은 두 가지 케이스를 고려해볼 수 있다. 

1) empty string과 null value(입력하지 않은 케이스)를 같은 케이스로 보고 싶을 때

2) empty string과 null value를 구분하고 싶을 때.

 

이렇게 말하면 이해가 어려우니 예제로 살펴보자.

 

아래와 같이 jsonDoc과 이를 담을 struct를 정의했다고 해보자. (여기서는 string을 사용)

type Config struct {
    Environment string
    Version     string
    HostName    string
}

jsonDoc :=`
  {
    "Environment" : "Dev",
    "Version" : ""
  }`

위 jsonDoc에는 HostName이 생략돼있는데, 위를 Unmarshal할 경우, Version과 Hostname 모두 ""(empty string)로 저장된다.

이는 string이 value이기 때문에, 비어 있을 경우 default로 ""를 가지기 때문이다. 

 

코드 전문

package main

import (
    "encoding/json"
    "fmt"
)

type Config struct {
    Environment string
    Version     string
    HostName    string

}

func (c *Config) String() string {
    return fmt.Sprintf("Environment: '%v'\nVersion:'%v'\nHostName: '%v'", 
    c.Environment, c.Version, c.HostName)
}

func main() {

    jsonDoc :=`
        {
            "Environment" : "Dev",
            "Version" : ""
        }`

    conf := &Config{}
    json.Unmarshal([]byte(jsonDoc), conf)
    fmt.Println(conf) // Prints 
                      //   Environment: 'Dev'
                      //   Version:''
                      //   HostName: ''

}

 

 

위 경우에는 HostName이 missing property인데,  이를 허용하고 싶지 않을 때는 어떻게 해야 할까?

 

아래처럼 처리할때 nil인지 아닌지를 확인해주고, nil일 때는 잘못된 값을 넣어줌으로써 처리할 수 없게 만들 수 있다. 

package main

import (
    "encoding/json"
    "fmt"
)

type ConfigWithPointers struct {
    Environment *string // pointer to string
    Version     *string
    HostName    *string
}

func (c *ConfigWithPointers) String() string {
    var envOut, verOut, hostOut string
    envOut = "<nil>"
    verOut = "<nil>"
    hostOut = "<nil>"

    if c.Environment != nil { // Check for nil!
        envOut = *c.Environment
    }

    if c.Version != nil {
        verOut = *c.Version
    }

    if c.HostName != nil {
        hostOut = *c.HostName
    }

    return fmt.Sprintf("Environment: '%v'\nVersion:'%v'\nHostName: '%v'",
        envOut, verOut, hostOut)
}

func main() {

    jsonDoc :=
        `
        {
            "environment" : "asdf",
            "hostName" : ""
        }
        `

    conf := &ConfigWithPointers{}
    json.Unmarshal([]byte(jsonDoc), conf)
    fmt.Println(conf) // Prints the following:
                      // Environment: 'asdf'
                      // Version:'<nil>'
                      // HostName: ''

}

 

위 예시를 보면, hostName = ""이, 아무것도 안쓴게 아니라, 실제로 ""로 들어가야 하는 경우에 사용된다. 즉 모든 value들이 다 적혀있는 jsonDoc만 유효하게 하고 싶을 경우 위처럼 처리할 수 있는 것이다. 

 

 

 

Comments