출처: https://budougumi0617.github.io/2020/02/01/go-named-type-and-type-alias/
Go는 기존의 타입에 새로운 이름을 붙이는 방법은 두 가지가 있다.
- type MyType int 라고 선언하는 Named type
- type MyType = int 라고 선언하는 Type alias
Tl; DR
- Go는 타입에 다른 이름을 붙이는 방법이 있다.
- Named type을 사용하면 완전히 다른 타입으로 취급
- 원시적인 타입으로 다른 타입 이름을 붙이거나 방법을 만들 수도있다
- Value Object적인 타입을 쉽게 만들 수있다
- Type alias를 사용하면 다른 이름이지만 같은 타입으로 취급
- 리팩토링을 할 때 사용된다
- 타입 붙임을 이용한 구분을 하고 싶다면 적합하지 않다
- 기본적으로 Named Type을 사용하면 된다.
이 문서는 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나 문자열 등의 취급 실수를 방지 할 수 있다.