출처: https://budougumi0617.github.io/2020/02/01/go-named-type-and-type-alias/

Go는 기존의 타입에 새로운 이름을 붙이는 방법은 두 가지가 있다.

Tl; DR

이 문서는 Go1.13를 사용하여 검증하고 있다(정확히 말하면 2020/02/01 시점의 Go Playground로 검증하고 있다).

Named type

Named type은 Go1.0에서 있는 기능이다.

어느 타입의 정의를 그대로 이용하여 완전히 다른 타입으로 인식되는 새로운 타입을 정의 할 수 있다.

아래 코드는 http.Request 타입을 사용한 새로운 MyRequest 타입을 선언하고, 메소드를 추가하고 있다.

https://play.golang.org/p/cG9o7W10IjF 

package main

import (
        
"fmt"
        
"net/http"
)

type MyRequest http.Request

func (mc MyRequest) MyFunc() {
        fmt.Println(
"in MyFunc", mc.Host)
}

func useMyRequest(mc MyRequest) {
        mc.MyFunc()
}

func main() {
        myReq := MyRequest{Host:
"http://google.com"}
        useMyRequest(myReq)
}

Named type은 원시적인 타입에 대해서도 이용할 수 있다.

Named type으로 선언한 새로운 타입과 원본이 된 타입은 호환성이 없기 때문에 Value Object으로 이용할 수 있다.

아래 예제에서는 사용자 이름과 이메일 주소의 문자열 처리를 실수하고 있다.

https://play.golang.org/p/lX26gpsz21T 

package main

import "fmt"

type User struct {
        Name, Email
string
}

func NewUser(name, email string) User { return User{Name: name, Email: email} }

func main() {
        name :=
"John Doe"
        mail :=
"example@foo.com"
        u := NewUser(mail, name)
// 순서가 틀렸다
        fmt.Println(u.Name)
// example@foo.com
}

Named type을 사용하여 타입 검사를 이용하여 잘못된 사용을 방지 할 수 있다.

https://play.golang.org/p/1S7VT6lT69J 

package main

import "fmt"

type Name string
type MailAddress string

type User struct {
        Name  Name
        Email MailAddress
}

func NewUser(name Name, email MailAddress) User { return User{Name: name, Email: email} }

func main() {
        name := Name(
"John Doe")
        mail := MailAddress(
"example@foo.com")
        
// cannot use mail (type MailAddress) as type Name in argument to NewUser
        
// cannot use name (type Name) as type MailAddress in argument to NewUser
        u := NewUser(mail, name)
        fmt.Println(u.Name)
}

type UserID int, type DocumentID int 와 같이 Named type을 사용하면 데이터베이스 관련으로 ID를 취급하고 실수도 없어진다.

또한 메소드를 추가 할 수 있으므로, 검증 등을 추가하는 것이 가능하다.

 

https://play.golang.org/p/7e85wA1DPex 

package main

import (
        
"fmt"
        
"log"
)

type Password string

func (p Password) Validate() error {
        
if len(p) < 16 {
                
return fmt.Errorf("패스워드는 16문자 이상")
        }
        
return nil
}

type User struct {
        Name    
string
        Password Password
}

func NewUser(n string, pw Password) (*User, error) {
        
if err := pw.Validate(); err != nil {
                
return nil, err
        }
        
return &User{n, pw}, nil
}

func main() {
        n :=
"John Doe"
        pw := Password(
"p@ssw0rd")
        u, err := NewUser(n, pw)
        
if err != nil {
                log.Fatal(err)
// 패스워드는 16 문자 이상
        }
        fmt.Println(u.Name)
}

타입 별칭 (Type alias)

타입 별칭은 Go1.9에서 추가된 기능이다.

Changes to the language | Go 1.9 Release Notes

타입 별칭은 주로 아래의 특징을 가진다.

다른 타입으로 사용할 수 있는 Named type와 달리 타입 별칭을 사용하면 동일한 타입으로 이용할 수 있다.

따라서 Named Type과 같은 타입 검사를 기대해도 이것은 할 수 없으므로 주의한다.

https://play.golang.org/p/VgpS3x89-bO 

package main

import "fmt"

type Name = string // type alias
type MailAddress = string // type alias

type User struct {
        Name  Name
        Email MailAddress
}

func NewUser(name Name, email MailAddress) User { return User{Name: name, Email: email} }

func main() {
        name := Name(
"John Doe")
        mail := MailAddress(
"example@foo.com")
        u := NewUser(mail, name)
// 타입 틀림으로 컴파일 에러는 되지 않는다!!!!!
        fmt.Println(u.Name)
// example@foo.com
}

마무리

Type alias의 설명은 생략했지만, Named type과 Type Alias에 대해 정리했다.

Go는 타입을 잘 사용하면 안전한 코딩을 할 수 있다. Named type을 사용하면 단 한줄로 새로운 타입을 선언 할 수 있고, ID나 문자열 등의 취급 실수를 방지 할 수 있다.