본문 바로가기

Programming/Go

Go의 타입 시스템

『Go in Action』의 내용을 기반으로 학습 및 정리했습니다.


타입

사용자 정의 타입(Struct)

struct는 여러 필드를 하나의 타입으로 묶어 표현하는 합성 타입(composite type)이다.

 
type user struct {
	name   string
	email  string
	number int
}
 

type 키워드 뒤에 새로운 타입의 이름을 정의하고 struct 키워드로 구조체를 선언한다.

제로값 초기화

var 키워드로 구조체 변수를 선언하면 모든 필드는 각 타입의 제로값(zero value)으로 초기화된다.

 
var bill user
 

실제로는 다음과 같은 상태가 된다.

 
user{
	name:   "",
	email:  "",
	number: 0,
}
 

구조체 리터럴

구조체를 생성하면서 원하는 값으로 초기화하려면 구조체 리터럴(struct literal)을 사용할 수 있다.

필드명을 명시하는 방법:

 
alice := user{
	name:   "Alice",
	email:  "alice@gmail.com",
	number: 1,
}
 

필드명을 생략하는 방법:

 
alice := user{
	"Alice",
	"alice@gmail.com",
	1,
}
 

필드명을 생략하면 선언 순서에 의존하므로 일반적으로는 첫 번째 방식을 권장한다.


강한 타입 시스템

Go는 강한 타입 시스템(Strong Typing)을 가진 언어이다.

기반 타입(base type)이 같더라도 서로 다른 타입으로 정의되었다면 암시적 형변환을 허용하지 않는다.

 
type Duration int64

func main() {
	var dur Duration

	dur = int64(1000) // 컴파일 에러
}
 

Duration의 기반 타입은 int64이지만, Go는 두 타입을 서로 다른 타입으로 취급한다.

따라서 명시적으로 변환해야 한다.

 
dur = Duration(1000)
 

다만 상수는 예외가 존재한다.

 
dur = 1000
 

1000은 타입이 정해지지 않은 상수(untyped constant)이므로 컴파일러가 필요한 타입으로 해석할 수 있다.

이러한 특성 덕분에 의도하지 않은 형변환으로 발생할 수 있는 버그를 컴파일 시점에 발견할 수 있다.


메서드

Go의 메서드는 특정 타입에 연결된 함수이다.

수신자(Receiver)

 
func (u user) notify() {
	fmt.Printf("이름: %s, 이메일: %s\n", u.name, u.email)
}
 

(u user) 부분을 수신자(receiver)라고 한다.

수신자를 통해 해당 타입에 메서드를 연결할 수 있다.

 
bill := user{
	name:  "Bill",
	email: "bill@gmail.com",
}

bill.notify()
 

 


값 수신자(Value Receiver)

 
func (u user) notify() {
	fmt.Println(u.name)
}
 

값 수신자는 메서드 호출 시 구조체의 복사본을 전달받는다.

따라서 메서드 내부에서 값을 변경해도 원본에는 영향을 주지 않는다.

 
func (u user) changeName() {
	u.name = "Alice"
}
 
 
bill.changeName()

fmt.Println(bill.name)
// Bill
 

포인터 수신자(Pointer Receiver)

 
func (u *user) changeName() {
	u.name = "Alice"
}
 

포인터 수신자는 원본 데이터를 직접 참조한다.

 
bill.changeName()

fmt.Println(bill.name)
// Alice
 

구조체의 상태를 변경해야 하는 메서드는 일반적으로 포인터 수신자를 사용한다.


타입 임베딩(Type Embedding)

타입 임베딩은 기존 타입을 새로운 구조체 내부에 포함시키는 기능이다.

 
type person struct {
	name string
}

type employee struct {
	person
	company string
}
 

person이 내부 타입(inner type), employee가 외부 타입(outer type)이다.

 
e := employee{
	person: person{name: "Tom"},
	company: "Google",
}

fmt.Println(e.name)
 

외부 타입은 내부 타입의 필드와 메서드에 직접 접근할 수 있다.

타입 임베딩은 객체지향 언어의 상속(inheritance)과 비슷한 효과를 제공하지만, Go는 상속 대신 조합(composition)을 권장한다.


외부 노출 식별자와 비노출 식별자

Go는 식별자의 첫 글자가 대문자인지 여부로 접근 가능 범위를 결정한다.

외부 노출 식별자(Exported Identifier)

type User struct {
	Name string
}
 

대문자로 시작하는 식별자는 다른 패키지에서도 접근할 수 있다.

비노출 식별자(Unexported Identifier)

type user struct {
	name string
}
 

소문자로 시작하는 식별자는 같은 패키지 내부에서만 사용할 수 있다.

Go에는 public, private 키워드가 존재하지 않으며, 대소문자 규칙만으로 접근 범위를 제어한다.

 


인터페이스 구현과 method set

주의할 점은 포인터 수신자 메서드를 가진 타입은 “포인터 타입만” 인터페이스를 구현한다는 것이다.

func (m *defaultMatcher) Search(feed *Feed, searchTerm string) {}

var dm defaultMatcher
var matcher Matcher = dm

// 컴파일 에러 cannot use dm as Matcher

반면 아래 코드는 정상 동작한다.

var matcher Matcher = &dm

 

'Programming > Go' 카테고리의 다른 글

Go의 동시성 패턴  (0) 2026.06.14
Go 런타임 스케줄러와 동시성 모델  (1) 2026.06.07
Package와 Go의 데이터 컬렉션  (0) 2026.05.31
Go의 특징과 핵심 살펴보기  (0) 2026.05.24