5장 Go의 타입 시스템
- Go는 정적 타입 프로그래밍 언어
- primitive 타입의 경우 이름에 바이트 크기가 같이 있는 경우가 많음. (int32, int64 등)
사용자 정의 타입 (composite type)
- c의 struct랑 비슷
type myType struct { field1 type1 field2 type2 }
변수로 선언하면 모든 필드가 제로값으로 초기화됨
제로값이 아니라 특정 값으로 초기화되어야하면 구조체 리터럴 및 변수 선언 연산자 사용
var1 := myType { field1: value1 field2: value2 }
var1 := myType{value1, value2}
중첩 가능
primitive 타입을 base type으로 새로운 타입 정의 가능
type Duration int64
base type과 완전히 다른 별개의 타입으로 취급
Method
- 타입에 행위를 정의하기 위한 방법. func 키워드와 함수 이름 사이에 추가 패러미터를 정의한 함수
- func와 함수 이름 사이의 패러미터는 특별히 receiver이라고 부름
- 그리고 이러한 receiver가 있는 함수는 method라고 지칭
- method의 경우 일반적인 함수의
Package.Function
대신에Type.Function
형태로 호출 - 이 타입이 method의 receiver으로 전달됨.
Value receiver vs Pointer receiver
- Value receiver
func (n myType) method() {}
- receiver의 값을 복사해서 method로 전달함. 따라서 method 내부에서 아무리 변경하더라도 원본에는 영향이 없음.
- pointer로도 바로 호출이 가능함. pointer를 사용해서 method를 호출하면 실질적으로는
(*myPointer).method()
와 동일 - 불변 객체를 유지하고싶다면 Value receiver가 답. 변경이 발생할 때마다 복제본이 반환됨.
- Pointer receiver
func (n *myType) method() {}
- receiver의 pointer를 복사해서 method로 전달. 덕분에 method 내부에서 변경하면 원본 인스턴스의 값도 같이 변경됨
- value으로도 바로 호출이 가능함. value을 사용해서 method를 호출하면
(&myValue).method()
와 동일 - 기존 인스턴스의 상태를 변경해야한다면 Pointer receiver가 답. 변경이 발생하면 원본이 변경됨.
내장 타입 (built-in type)
- 언어 차원에서 지원되는 타입들
- 숫자, 문자열, boolean 등
- primitive type이라고 말할 수도 있음
- 뭔가 추가하거나 제거하면 기본값이 변경되지 않고 반드시 새로운 값 생성
- 함수나 메소드에 전달할 때 복사된 value이 전달됨
- built-in type에 대해서 method를 선언할 수 없음
- built-in type을 base type으로 새로운 사용자 정의 타입을 선언해야함
참조 타입 (reference type)
- 슬라이스, 맵, 채널, 인터페이스, 함수 타입 등
- 변수와 함께 헤더 value 생성
- 헤더는 해당 타입의 데이터 구조를 관리하기 위해 여러 pointer, value들을 필드로 가지고 있음
- 슬라이스로 치면 length, capacity 및 내장 배열 pointer 등
- 헤더는 함수로 전달하거나 리시버로 호출할 때 value 복사를 전제로 깔고 설계되었기 때문에 pointer로 쓰면 안됨
- 문자열 또한 기술적으로는 참조 타입
구조체 타입
- primitive, non-primitive 성질을 모두 가질 수 있음
- 원하는 성질에 따라 built-in type, reference type을 위한 가이드라인을 준수해야 함
- 가변 객체의 경우 메소드나 함수, 리시버에서 pointer를 사용
- 불변 객체의 경우 메소드나 함수, 리시버에서 value 사용
- 인터페이스 value이 다른 경우 value 타입 리시버에게 유연성을 제공해야하는게 유일한 예외
인터페이스 타입
- 다형성을 위해서 사용
- 행위를 선언하기 위한 타입
- 사용자 정의 타입이 인터페이스에 정의된 행위를 메소드 형태로 구현해야 함 <- 구현 타입
- 인터페이스 메소드를 구현한 사용자 정의 타입은 (== 메소드 집합에 동일하게 모두 있어야 함) 인터페이스 value에 대입 가능
- 인터페이스는 2개의 word로 구성
- 첫번째 word는 iTable에 대한 주소. iTable에는 저장된 데이터의 타입 정보, 메소드 집합 저장
- 두번째 word는 실제 value에 대한 주소
메소드 집합
- 주어진 타입 value이나 pointer에 관련된 메소드 집합. receiver의 종류에 따라 메소드가 value이냐 pointer냐가 정해짐
- value는 value receiver를 가진 메소드들만 메소드 집합으로 포함
- value의 주소를 확인할 수 없는 경우도 존재하기 때문 -> go에서 자동으로 바꿔서 실행시킬 수 없음
- pointer는 value, pointer receiver 모두 메소드 집합으로 포함
다형성
- 어떤 타입이든 인터페이스를 구현할 수 있음
- 구현 타입이 인터페이스에 정의된 메소드를 구현하기만 하면 실제 타입이 뭐든간에 자유롭게 전달, 캐스팅이 가능
타입 임베딩
- 기존의 타입을 확장하거나 동작을 변경
type outer struct { inner }
- 기존에 선언된 타입을 새로운 구조체 타입의 내부에 선언하는 것
- 새로운 타입은 특별히 outer type, 내부에 들어간 타입은 inner type이라고 지칭
- inner type의 필드, 메소드 등 모든 것들이 outer type에게 적용됨. 덕분에 inner type의 구현한 인터페이스는 outer type도 자동으로 구현하게 됨
- outer type에서 재정의해서 오버라이딩도 가능
- inner type을 직접 접근해서 호출하는 것도 가능
노출, 비노출 식별자
- 소문자로 시작하면 비노출
- 대문자로 시작하면 노출
- 만약 비노출 필드에 대해서 팩토리 함수를 제공한다면
New
로 이름 짓는게 규칙- 노출되지 않은 타입을 명시적으로 선언하는건 불가능하지만 단축 변수 선언 연산자로 정의하는 것은 가능
- 식별자는 값이 아니기 때문
- 단축 변수 선언 연산자는 타입을 추정하여 비노출 타입의 변수를 생성할 수 있기 때문
- inner type이 비노출이더라도 outer type이 노출이면 outer type을 통해 접근 가능.
- 이 경우 구조체 리터럴을 써서 inner type을 초기화할 순 없음
- inner type을 직접 호출하는 것도 불가능