본문 바로가기
프로그래밍/Golang

Golang type(타입) 키워드 탐구 : Named Type과 Type Alias의 차이와 활용

by slowin 2024. 7. 21.

type 키워드는 새로운 타입을 정의하는 데 사용되는 중요한 키워드입니다.

기본 타입 정의

type 키워드를 사용하여 기존 타입을 기반으로 새로운 타입을 정의할 수 있습니다.

type MyInt int
type MyString string

 

이렇게 정의된 타입은 원래 타입과 구조적으로 동일하지만, Go 컴파일러는 이를 다른 타입으로 취급합니다.

 

구조체 정의

type을 사용하여 구조체를 정의할 수 있습니다.

type Person struct {
    Name string
    Age  int
}

 

인터페이스 정의

인터페이스도 type 키워드로 정의합니다.

type Printer interface {
    Print() string
}

 

함수 타입 정의

함수 시그니처를 새로운 타입으로 정의할 수 있습니다.

type MathFunc func(int, int) int

 

예제:

// MathFunc는 두 개의 int를 받아 하나의 int를 반환하는 함수 타입입니다.
type MathFunc func(int, int) int

// 덧셈 함수
func add(a, b int) int {
	return a + b
}

// 뺄셈 함수
func subtract(a, b int) int {
	return a - b
}

// MathFunc 타입의 함수를 인자로 받아 실행하는 함수
func executeMathOperation(f MathFunc, x, y int) int {
	return f(x, y)
}

func main() {
	// MathFunc 타입의 변수에 함수 할당
	var operation MathFunc

	// 덧셈 연산
	operation = add
	executeMathOperation(operation, 10, 5)

	// 뺄셈 연산
	operation = subtract
	executeMathOperation(operation, 10, 5)

	// 익명 함수를 직접 MathFunc로 사용
	executeMathOperation(func(a, b int) int {
		return a / b // 나눗셈
	}, 10, 5)
}

 

타입 별칭 (Type Alias)

Go 1.9부터는 타입 별칭을 정의할 수 있습니다.

type MyAlias = string

MyAlias가 string의 또 다른 이름일 뿐임을 나타냅니다.

 

Named Type

  • Named Type (MyType)은 완전히 새로운 타입입니다.
  • 이는 타입 안전성을 보장하기 위함입니다.
var a int = 5
var b MyInt = 5

// a = b  // 컴파일 에러: cannot use b (type MyInt) as type int in assignment

메서드 세트와 타입

Named Type을 정의할 때, 해당 타입에 대한 메서드를 정의할 수 있습니다.

type MyInt int

func (m MyInt) Double() MyInt {
    return m * 2
}

이렇게 정의된 메서드는 원본 타입 (int)에는 적용되지 않고, 새로 정의된 타입 (MyInt)에만 적용됩니다.

 

타입 임베딩 (Type Embedding)

구조체 내부에 다른 타입을 임베드할 수 있습니다.

type Employee struct {
    Person
    JobTitle string
}

이 경우 Employee는 Person의 모든 필드와 메서드를 상속받게 됩니다.

 

타입 임베딩 (Type Embedding) 사용 시 주의사항

1. 이름 충돌

임베디드 타입과 외부 타입이 동일한 이름의 필드나 메서드를 가질 경우, 외부 타입의 필드나 메서드가 우선됩니다.

type A struct {
    Name string
}

type B struct {
    A
    Name string  // 이 필드가 A의 Name 필드를 가림
}

func main() {
    b := B{A: A{Name: "Inner"}, Name: "Outer"}
    fmt.Println(b.Name)  // "Outer" 출력
    fmt.Println(b.A.Name)  // "Inner" 출력
}

 

2. 임베디드 포인터 타입의 nil 체크

포인터 타입을 임베드할 경우, nil 체크를 주의해야 합니다.

type A struct {
	name string
}

func (a *A) Speak() {
	fmt.Println(a.name) // 패닉 발생: nil 포인터 역참조
}

type B struct {
	*A
}

func TestConstraint(t *testing.T) {
	var b B
	b.Speak()
}

 

마지막으로 Named type과 Type Alias를 좀 더 알아보겠습니다.

Named Type vs Type Alias

Named Type과 Type Alias의 차이점을 이해하는 것이 중요합니다.

type MyType string       // Named Type
type MyAlias = string    // Type Alias

타입 동일성

  • Type Alias: 원본 타입과 완전히 동일한 타입으로 취급됩니다.
  • Named Type: 원본 타입과 다른 새로운 타입으로 취급됩니다.
type MyString string  // Named Type
type MyAlias = string // Type Alias

var s string = "hello"
var ms MyString = "hello"
var ma MyAlias = "hello"

// s = ms     // 컴파일 에러
s = ma        // 가능

메서드 세트

  • Type Alias: 원본 타입의 모든 메서드를 그대로 사용할 수 있습니다.
  • Named Type: 독립적인 메서드 세트를 가집니다.
type AgeNamed int
type AgeAlias = int

// 정상
func (a AgeNamed) IsAdult() bool {
	return a >= 18
}

// 컴파일에러 
// Invalid receiver type 'int' ('int' is a non-local type)
func (a AgeAlias) IsAdult() bool {
	return a >= 18
}

타입 전환(Type Assertion)

  • Type Alias: 원본 타입과 동일하게 취급되어 타입 전환이 필요 없습니다.
  • Named Type: 명시적인 타입 전환이 필요합니다.
	type AliasMyInt = int // Alias Type
	var ay int = 20
	var ax AliasMyInt = 10

	// 직접 할당 가능
	ax = ay
	ay = ax

	type NamedMyInt int // Named Type
	var x NamedMyInt = 10
	var y int = 20

	// 직접 할당 불가능
	// x = y  // 컴파일 에러
	// y = x  // 컴파일 에러

	// 명시적 타입 전환 필요
	x = NamedMyInt(y)
	y = int(x)

 

Named Type은 새로운 타입을 만들어 타입 안전성과 의미론적 명확성을 높이고, Alias Type은 기존 타입을 유연하게 사용할 수 있게 해줍니다.

 

 

참고

spec: embedding a type alias is confusing #17746

https://go.googlesource.com/proposal/+/master/design/18130-type-alias.md

https://go.dev/talks/2016/refactor.article