
컴파일 에러 앞에 멈춰버린 당신을 위한 Go 자료형 트러블슈팅
분명히 머릿속으로는 완벽한 로직을 짰다고 생각했어요. 그런데 막상 코드를 실행하려고 하면 화면 가득 빨간 줄이 뜨면서 “cannot use x (type int) as type float64 in assignment” 같은 무시무시한 메시지가 나타나죠. 이럴 때면 Go 언어가 유독 까다롭고 불친절하다는 생각이 들기 마련이에요.
다른 언어에서는 아무렇지도 않게 넘어가던 숫자 더하기가 Go에서는 왜 이렇게 힘든 싸움이 되는 걸까요? 그 이유는 Go가 가진 매우 엄격한 타입 시스템 때문이에요. Go는 타입에 대해 타협하지 않아요. 정수와 실수를 섞어 쓰는 것도, 32비트와 64비트 숫자를 그냥 더하는 것도 허용하지 않죠.
이런 에러를 마주할 때마다 구글링을 반복하며 시간을 허비하고 있다면, 이제는 근본적인 원인을 이해해야 할 때예요. 타입을 단순히 데이터의 종류로만 보지 말고, 메모리에 데이터가 어떻게 담기는지 그 원리를 알아야 트러블슈팅이 가능해져요. 오늘 이 글을 끝까지 읽고 나면, 더 이상 타입 에러 때문에 당황하지 않고 능숙하게 코드를 작성할 수 있게 될 거예요.
이 가이드에서는 다음과 같은 내용들을 깊이 있게 다뤄요.
- Go 자료형이 왜 다른 언어보다 까다로운지에 대한 근본적 이유
- 실무에서 가장 빈번하게 발생하는 숫자 타입 변환 에러 패턴
- 문자열과 바이트, 룬(rune) 사이의 복잡한 관계 정리
- 인터페이스를 사용할 때 발생하는 타입 단언 에러 해결법
트러블슈팅 전 반드시 점검해야 할 Go 자료형 기초
문제를 해결하기 전에 우리가 발을 딛고 있는 지형을 먼저 파악해야 해요. Go의 자료형은 크게 정수, 실수, 문자열, 불리언, 그리고 복합 자료형으로 나뉘어요. 하지만 여기서 중요한 건 단순히 이름이 아니라, 각 자료형이 메모리에서 차지하는 크기와 범위라는 점이에요.
예를 들어, int 타입은 운영체제의 아키텍처에 따라 크기가 변해요. 64비트 시스템에서는 64비트지만, 32비트 시스템에서는 32비트가 되죠. 반면 int64는 어떤 환경에서도 항상 64비트를 유지해요. 이런 미세한 차이가 대규모 데이터를 처리할 때 예상치 못한 오버플로우(Overflow) 에러를 일으키는 주범이 돼요.
Go에서 자료형 변환은 ‘명시적’이어야 해요. 다른 언어처럼 컴파일러가 알아서 타입을 맞춰주는 ‘암시적 형변환’을 지원하지 않는다는 점을 항상 기억하세요.
본격적인 코딩에 들어가기 전, 내가 사용하려는 데이터의 성격에 따라 어떤 타입을 선택해야 할지 기준을 세워두는 것이 좋아요. 아래 표를 보고 상황에 맞는 타입을 결정하는 연습을 해보세요.
| 데이터 성격 | 추천 자료형 | 선택 기준 및 주의점 |
|---|---|---|
| 일반적인 정수 계산 | int | 플랫폼 크기에 의존하므로 범용적 계산에 적합해요. |
| 대규모 ID 또는 시각 정보 | int64 | 어떤 환경에서도 데이터 유실이 없도록 보장해요. |
| 정밀한 소수점 계산 | float64 | float32보다 정밀도가 높지만 메모리 사용량이 많아요. |
| 부호 없는 카운터 | uint | 음수가 될 일이 없는 경우에만 사용하세요. |
준비물은 간단해요. 타입의 크기를 계산해 볼 수 있는 머릿속의 논리력과, 컴파일 에러 메시지를 두려워하지 않는 마음가짐이면 충분해요. 이제 실제 상황에서 어떤 에러가 발생하는지 본격적으로 파헤쳐 볼게요.
자주 발생하는 타입 에러 패턴과 실전 해결 전략
이제 본격적으로 Go 개발자들을 괴롭히는 실전 사례들을 하나씩 살펴볼게요. 단순히 에러를 고치는 법을 넘어, 왜 이런 구조를 가졌는지 이해하는 것이 핵심이에요.
STEP 1. 숫자 타입 간의 불일치 해결하기
가장 흔한 상황은 정수와 실수를 함께 계산하려고 할 때 발생해요. 예를 들어, 사용자 점수를 정수로 저장해 두었는데, 평균을 내기 위해 실수로 나누려 하면 컴파일 에러가 발생하죠. Go는 정수와 실수를 섞어서 연산하는 것을 절대 허용하지 않아요.
예를 들어 볼게요. total := 10; avg := total / 2.5 라고 쓰면 컴파일러는 즉시 화를 낼 거예요. 이때는 명시적 형변환(Explicit Conversion)을 사용해야 해요. avg := float64(total) / 2.5 처럼 정수를 먼저 실수 타입으로 바꿔주어야 하죠.
형변환을 할 때는 데이터 손실에 주의해야 해요. 큰 실수를 작은 정수 타입으로 바꾸면 소수점 이하 데이터가 모두 사라져 버리니까요.
STEP 2. 플랫폼 의존적인 int 타입 다루기
많은 입문자가 int를 만능으로 생각하곤 해요. 하지만 API 응답 데이터나 데이터베이스에서 가져온 값이 int64로 지정되어 있다면, 아무리 똑같은 숫자라도 int 변수에 바로 담을 수 없어요. 두 타입은 서로 다른 타입으로 간주되기 때문이에요.
이런 경우에는 반드시 int(yourInt64Variable)와 같이 변환 과정을 거쳐야 해요. 만약 값이 64비트 범위를 넘어설 가능성이 있다면, 섣불리 int로 바꾸지 말고 그대로 int64를 사용하는 것이 훨씬 안전한 설계예요.
STEP 3. 문자열과 바이트의 미묘한 경계선
문자열을 다룰 때도 트러블슈팅이 필요해요. Go에서 string은 읽기 전용 바이트 슬라이스라고 생각하면 편해요. 네트워크 통신이나 파일 입출력을 할 때는 보통 []byte 타입을 사용하는데, 이를 문자열로 바꾸거나 그 반대로 바꿀 때 에러가 잦아요.
가장 권장되는 방법은 string(byteSlice) 또는 []byte(str)를 사용하는 것이에요. 하지만 한글과 같은 멀티바이트 문자를 다룰 때는 단순 바이트 변환이 문자를 깨뜨릴 수 있어요. 이때는 rune 타입을 활용해 유니코드 포인트를 직접 다루는 것이 훨씬 정교한 방법이에요.
STEP 4. 인터페이스와 타입 단언(Type Assertion)
인터페이스(interface{})는 Go의 강력한 무기지만, 동시에 가장 위험한 칼날이기도 해요. 어떤 값이 인터페이스 타입에 담겨 있을 때, 그 실제 값이 무엇인지 알아내기 위해 타입 단언을 사용하게 되죠. 만약 단언한 타입이 실제 타입과 다르면 프로그램은 즉시 panic을 일으키며 멈춰버려요.
이를 방지하기 위한 실무적인 시나리오는 다음과 같아요. v, ok := i.(int)와 같이 두 번째 반환 값인 ok를 반드시 확인하는 습관을 들여야 해요. ok가 false라면 타입이 일치하지 않는 것이니, 안전하게 예외 처리를 할 수 있답니다.
STEP 5. 실전 통합 시나리오: 데이터 처리 흐름
이제 배운 내용들을 종합해 볼게요. 외부 API에서 JSON 데이터를 받아온다고 가정해 봅시다. JSON 엔진은 숫자를 보통 float64로 해석하는 경우가 많아요. 하지만 우리가 필요한 값은 사용자 나이(int)라면 어떻게 해야 할까요?
먼저 JSON 데이터를 구조체에 담을 때 해당 필드를 float64로 정의한 뒤, 로직에서 사용할 때 int(age)로 변환해 주는 과정이 필요해요. 이 흐름을 이해하면 데이터 흐름의 단계마다 어디서 에러가 날지 미리 예측할 수 있어요.
타입 변환을 너무 남발하면 코드의 가독성이 떨어지고, 예상치 못한 데이터 손실(Truncation)이 발생할 수 있어요. 가급적 처음부터 데이터의 성격에 맞는 타입을 설계하는 것이 최선이에요.
자주 하는 실수와 해결법 및 FAQ
자주 하는 실수와 해결법
개발 과정에서 흔히 저지르는 실수들을 모아봤어요. 코드를 작성하다가 막히면 이 목록을 확인해 보세요.
- ❌ 정수와 실수를 그냥 더하려고 할 때
왜 발생하는가: Go는 타입 간의 자동 형변환을 지원하지 않기 때문이에요.
✅ 해결법:float64(intVariable) + floatVariable처럼 명시적으로 변환하세요. - ❌ 문자열 숫자를 정수로 바로 바꾸려 할 때
왜 발생하는가:int("123")은 문법적으로 불가능해요. 타입 변환은 메모리 레이아웃을 바꾸는 것이지, 내용을 해석하는 게 아니거든요.
✅ 해결법: strconv 패키지의Atoi()함수를 사용하세요. - ❌ 인터페이스를 안전하지 않게 단언할 때
왜 발생하는가: 예상치 못한 타입이 들어오면 프로그램이 중단(Panic)돼요.
✅ 해결법:val, ok := i.(Type)패턴을 사용하여ok값을 체크하세요. - ❌ 큰 숫자를 작은 타입에 담을 때
왜 발생하는가: 정수 오버플로우가 발생하여 값이 완전히 왜곡될 수 있어요.
✅ 해결법: 데이터의 최대 범위를 계산해 보고, 충분히 큰int64나uint64를 사용하세요. - ❌ 문자열 슬라이싱 시 한글이 깨질 때
왜 발생하는가: 한글은 한 글자가 여러 바이트를 차지하는 유니코드이기 때문이에요.
✅ 해결법: 슬라이스를[]rune으로 변환하여 인덱스로 접근하세요.
자주 묻는 질문
Q. int와 int64는 왜 서로 호환되지 않나요?
Go는 컴파일 타임에 데이터의 크기를 명확히 하여 안전성을 높이는 것을 지향해요. int는 시스템에 따라 32비트일 수도 있는데, 64비트 데이터를 잘못 담으면 데이터가 잘릴 수 있기 때문이에요.
Q. float64를 int로 바꾸면 소수점은 어떻게 되나요?
소수점 이하 자릿수는 반올림되지 않고 그냥 버림(Truncation) 처리가 돼요. 3.9를 4로 만들고 싶다면 math.Round() 함수를 먼저 사용해야 해요.
Q. 0의 값이 자료형마다 다른가요?
모든 숫자 자료형의 제로 값(Zero value)은 0이지만, 포인터나 인터페이스, 슬라이스 같은 참조형의 제로 값은 nil이라는 점을 꼭 구분해야 해요.
Q. 왜 Go에는 다른 언어처럼 ‘any’ 타입이 없나요?
사실 Go 1.18 버전부터 any라는 키워드가 도입되었어요. 이는 interface{}의 별칭(Alias)이며, 어떤 타입이든 담을 수 있게 해줘요. 하지만 남용하면 타입 안정성을 잃게 되니 주의가 필요해요.
자료형 마스터를 위한 마지막 체크리스트
오늘 배운 내용을 바탕으로, 코드를 작성한 후 스스로에게 다음 질문들을 던져보세요. 이 습관만 들여도 트러블슈팅 시간의 80%를 줄일 수 있어요.
- 모든 숫자 연산 시 타입이 일치하는지 확인했나요?
- 형변환 시 데이터 손실(소수점 버림, 오버플로우) 가능성을 고려했나요?
- 인터페이스 타입 단언 시 반드시
ok값을 체크했나요? - 문자열을 다룰 때 유니코드(rune) 처리가 필요한 상황인지 확인했나요?
- 플랫폼에 영향을 받는
int대신 명확한int64가 필요한 곳은 없나요?
이제 이론은 충분해요. 직접 에러를 마주하며 코드를 짜보는 것만큼 좋은 공부는 없답니다. 오늘 바로 진행 중인 프로젝트에서 타입 선언 부분을 다시 한번 점검해 보는 건 어떨까요?
오늘 할 일: 현재 작성 중인 코드에서 int와 float64가 혼용된 곳이 있는지 찾아보세요.
이번 주 할 일: strconv 패키지를 활용해 다양한 문자열 변환 예제를 만들어 보세요.
혹시 실습 중에 이해가 안 가거나 해결되지 않는 에러 메시지가 있다면 언제든 댓글로 남겨 주세요. 여러분의 성장을 진심으로 응원할게요!
함께 읽으면 좋은 글: /programming/golang/go-data-types-complete-guide/