>  기사  >  백엔드 개발  >  golang: nil 포인터와 nil 인터페이스의 차이점 이해하기

golang: nil 포인터와 nil 인터페이스의 차이점 이해하기

WBOY
WBOY원래의
2024-07-18 08:51:19731검색

golang: Understanding the difference between nil pointers and nil interfaces

go에서 nil이 작동하는 다양한 방식과 때로는 nil이면서 동시에 nil이 아닐 수 있는 방법에 대해 조금 생각하고 있었습니다.

다음은 nil 포인터가 될 수 있지만 nil 인터페이스는 될 수 없는 것에 대한 작은 예입니다. 이것이 무엇을 의미하는지 살펴보겠습니다.

인터페이스

첫째, Go에는 인터페이스 개념이 있습니다. 이는 일부 객체 지향 언어의 인터페이스와 유사하지만 완전히 동일하지는 않습니다(go는 대부분의 정의에서 OOP가 아닙니다). Go에서 인터페이스는 인터페이스를 만족시키기 위해 다른 유형이 구현해야 하는 기능을 정의하는 유형입니다. 이를 통해 다양한 방식으로 인터페이스를 만족시킬 수 있는 여러 구체적인 유형을 가질 수 있습니다.

예를 들어 error는 단일 메소드가 있는 내장 인터페이스입니다. 다음과 같습니다:

type error interface {
    Error() string
}

오류로 사용하려는 모든 유형에는 문자열을 반환하는 Error라는 메서드가 있어야 합니다. 예를 들어 다음 코드를 사용할 수 있습니다.

type ErrorMessage string

func (em ErrorMessage) Error() string {
    return string(em)
}

func DoSomething() error {
    // Try to do something, and it fails.
    if somethingFailed {
        var err ErrorMessage = "This failed"
        return err
    }
    return nil
}

func main() {
    err := DoSomething()
    if err != nil {
        panic(err)
    }
}

이 예에서는 문제가 발생하면 DoSomething이 오류를 반환한다는 점에 유의하세요. ErrorMessage 유형을 사용할 수 있는 이유는 문자열을 반환하여 오류 인터페이스를 구현하는 Error 함수가 있기 때문입니다.
오류가 발생하지 않으면 nil을 반환했습니다.

포인터

Go에서 포인터는 값을 가리키지만 값이 없을 수도 있습니다. 이 경우 포인터는 nil입니다. 예:

var i *int = nil

func main() {
    if i == nil {
        j := 5
        i = &j
    }
    fmt.Println("i is", *i)
}

이 경우 i 변수는 int에 대한 포인터입니다. int를 생성하여 이를 가리킬 때까지 nil 포인터로 시작합니다.

포인터와 인터페이스

사용자 정의 유형에는 함수(메서드)가 첨부될 수 있으므로 유형을 가리키는 함수도 있을 수 있습니다. 이것은 go에서 매우 일반적인 관행입니다. 이는 또한 포인터가 인터페이스를 구현할 수도 있음을 의미합니다. 이런 방식으로 우리는 nil이 아닌 인터페이스이지만 여전히 nil 포인터인 값을 가질 수 있습니다. 다음 코드를 고려해보세요:

type TruthGetter interface {
    IsTrue() bool
}

func PrintIfTrue(tg TruthGetter) {
    if tg == nil {
        fmt.Println("I can't tell if it's true")
        return
    }
    if tg.IsTrue() {
        fmt.Println("It's true")
    } else {
        fmt.Println("It's not true")
    }
}

IsTrue() bool 메소드가 있는 모든 유형은 PrintIfTrue에 전달될 수 있지만 nil도 가능합니다. 따라서 PrintIfTrue(nil)을 수행하면 "나는 그것이 사실인지 알 수 없습니다"라고 인쇄할 것입니다.

다음과 같이 간단한 작업도 수행할 수 있습니다.

type Truthy bool

func (ty Truthy) IsTrue() bool {
    return bool(ty)
}

func main() {
    var ty Truthy = true
    PrintIfTrue(ty)
}

이렇게 하면 "사실입니다"가 인쇄됩니다.

또는 다음과 같이 좀 더 복잡한 작업을 수행할 수도 있습니다.

type TruthyNumber int

func (tn TruthyNumber) IsTrue() bool {
    return tn > 0
}

func main() {
    var tn TruthyNumber = -4
    PrintIfTrue(tn)
}

그러면 "사실이 아닙니다"가 인쇄됩니다. 이 예제 중 어느 것도 포인터가 아니므로 이러한 유형 중 하나에 nil이 발생할 가능성은 없지만 다음을 고려하십시오.

type TruthyPerson struct {
    FirstName string
    LastName string
}

func (tp *TruthyPerson) IsTrue() bool {
    return tp.FirstName != "" && tp.LastName != ""
}

이 경우 TruthyPerson은 TruthGetter를 구현하지 않지만 *TruthyPerson은 구현합니다. 따라서 다음과 같이 작동합니다.

func main() {
    tp := &TruthyPerson{"Jon", "Grady"}
    PrintIfTrue(tp)
}

이것은 tp가 TruthyPerson에 대한 포인터이기 때문에 작동합니다. 그러나 포인터가 nil이면 당황하게 됩니다.

func main() {
    var tp *TruthyPerson
    PrintIfTrue(tp)
}

이렇게 되면 패닉 상태가 됩니다. 그러나 PrintIfTrue에서는 패닉이 발생하지 않습니다. PrintIfTrue가 nil을 확인하기 때문에 괜찮다고 생각할 것입니다. 그러나 여기에 문제가 있습니다. TruthGetter에 대해 nil을 확인하고 있습니다. 즉, nil 인터페이스는 확인하지만 nil 포인터는 확인하지 않습니다. 그리고 func (tp *TruthyPerson) IsTrue() bool에서는 nil을 확인하지 않습니다. 이동 중에도 nil 포인터에 대한 메서드를 호출할 수 있으므로 패닉이 발생합니다. 수정은 실제로 매우 쉽습니다.

func (tp *TruthyPerson) IsTrue() bool {
    if tp == nil {
        return false
    }
    return tp.FirstName != "" && tp.LastName != ""
}

이제 PrintIfTrue에서 nil 인터페이스를 확인하고 func (tp *TruthyPerson) IsTrue() bool에서 nil 포인터를 확인하고 있습니다. 이제 "사실이 아닙니다"가 인쇄됩니다. 여기에서 작동하는 모든 코드를 볼 수 있습니다.

보너스: 리플렉션을 사용하여 두 nil을 동시에 확인하세요.

리플렉션을 사용하면 PrintIfTrue를 약간 변경하여 nil 인터페이스와 nil 포인터를 모두 확인할 수 있습니다. 코드는 다음과 같습니다.

func PrintIfTrue(tg TruthGetter) {
    if tg == nil {
        fmt.Println("I can't tell if it's true")
        return
    }

    val := reflect.ValueOf(tg)
    k := val.Kind()
    if (k == reflect.Pointer || k == reflect.Chan || k == reflect.Func || k == reflect.Map || k == reflect.Slice) && val.IsNil() {
        fmt.Println("I can't tell if it's true")
        return
    }

    if tg.IsTrue() {
        fmt.Println("It's true")
    } else {
        fmt.Println("It's not true")
    }
}

여기에서는 이전과 마찬가지로 먼저 nil 인터페이스를 확인합니다. 다음으로, 리플렉션을 사용하여 유형을 가져옵니다. 포인터 외에 chan, func, map 및 Slice도 nil이 될 수 있으므로 값이 해당 유형 중 하나인지 확인하고, 그렇다면 nil인지 확인합니다. 그리고 만약 그렇다면, "나는 그것이 사실인지 알 수 없습니다"라는 메시지도 반환합니다. 이는 정확히 원하는 것일 수도 있고 아닐 수도 있지만 선택 사항입니다. 이번 변경을 통해 다음과 같은 작업을 수행할 수 있습니다.

func main() {
    var tp *TruthyPerson
    PrintIfTrue(tp)
}

가끔 다음과 같이 더 간단한 제안을 볼 수도 있습니다.

// Don't do this
if tg == nil && reflect.ValueOf(tg).IsNil() {
    fmt.Println("I can't tell if it's true")
    return
}

이것이 잘 작동하지 않는 데에는 두 가지 이유가 있습니다. 첫째, 리플렉션을 사용할 때 성능 오버헤드가 있다는 것입니다. 리플렉션 사용을 피할 수 있다면 아마도 그렇게 해야 할 것입니다. nil 인터페이스를 먼저 확인하면 nil 인터페이스라면 리플렉션을 사용할 필요가 없습니다.

두 번째 이유는 값의 유형이 nil이 될 수 있는 유형이 아닌 경우 Reflect.Value.IsNil()이 패닉을 일으키기 때문입니다. 이것이 바로 종류에 대한 수표를 추가하는 이유입니다. Kind를 확인하지 않았다면 Truthy 및 TruthyNumber 유형에 대해 당황했을 것입니다.

따라서 종류를 먼저 확인하면 이제 "사실이 아닙니다" 대신 "사실인지 알 수 없습니다"가 인쇄됩니다. 관점에 따라 이것이 개선될 수도 있습니다. 이러한 변경 사항이 적용된 전체 코드는 다음과 같습니다.

원래 Dan's Musings에 게시된 내용입니다

위 내용은 golang: nil 포인터와 nil 인터페이스의 차이점 이해하기의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.