1. 문제 정의

Go로 처음 코딩을 시작하면 흔히 부딪히는 컴파일 에러가 있습니다. 변수나 패키지를 선언했지만 아직 사용하지 않으면 빌드가 멈춥니다.

1
2
./main.go:6:7: i declared and not used
./main.go:3:8: imported and not used: "fmt"

사용자가 “나중에 쓰려고 선언해 둔 것뿐인데 왜 빌드를 막느냐"라고 느끼는 지점이 바로 이 에러입니다. 실제 StackOverflow 질문(21743841, 490점)도 같은 불만에서 시작합니다.

  • 증상 fingerprint
    • 에러 메시지: XXX declared and not used, XXX imported and not used: "패키지"
    • 발생 단계: go build / go run 컴파일 시점
    • 관련 도구: Go 컴파일러 (gc), Go 1.x 전체
    • 흔한 오해: “에러를 끄는 플래그가 있을 것이다”, “임시로 선언해 두면 되는 것이 아닌가”
    • 빠른 판단 기준: 에러에 declared and not used가 보이면 빈 식별자 _ 문제, imported and not used가 보이면 임포트 문제

2. 최소 재현 코드

에러를 가장 짧게 재현하는 코드는 다음과 같습니다.

1
2
3
4
5
6
7
package main

import "fmt" // imported and not used: "fmt"

func main() {
	i := 1 // i declared and not used
}
1
2
3
$ go build
./main.go:3:8: imported and not used: "fmt"
./main.go:6:7: i declared and not used

임포트된 fmt를 한 번도 쓰지 않고, 선언한 i를 한 번도 읽지 않았기 때문에 두 에러가 동시에 발생합니다.

3. 에러 발생 이유

이 에러는 버그가 아니라 Go 팀의 공식 설계 결정입니다. Go FAQ에는 다음과 같은 명시가 있습니다.

미사용 변수의 존재는 버그를 암시할 수 있고, 미사용 임포트는 컴파일 속도를 늦춘다. 코드 트리에 미사용 임포트가 충분히 쌓이면 전체가 매우 느려질 수 있다. 이런 이유로 Go는 둘 다 허용하지 않는다.

즉 두 가지 목적이 있습니다.

목적미사용 변수미사용 임포트
코드 품질버그 가능성 조기 발견 (선언만 하고 안 쓰는 로직은 의심)불필요한 의존성 제거
성능미사용 코드로 인한 혼란 방지컴파일/링크 속도 저하 방지
가독성“선언된 변수는 반드시 쓰인다"는 일관성 보장남이 읽어도 의도가 명확
  • 원인 후보 매트릭스 (root_cause_matrix)
원인 후보확인 방법해당 시 증상해결 방향
변수 선언 후 미사용에러의 파일·라인 확인i declared and not used실제 사용하거나 빈 식별자 _ = i
임포트 후 미사용go vet / 에러 메시지imported and not used: "pkg"임포트 제거 또는 _ "pkg"
시험 삼아 주석 처리한 코드에러가 사라진 직후 발생미사용 선언이 연쇄 발생나중에 쓸 예정이면 _로 임시 우회

4. 수정 코드

방법 A — 빈 식별자 _ 사용 (임시 우회)

빈 식별자 _에 대입하면 “일부러 안 쓴다"는 의도를 컴파일러에 전달해 에러를 없앱니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
package main

import (
	_ "fmt" // 임시로 사용 보류
)

func main() {
	i := 1
	_ = i // 임시로 사용 보류
}
1
$ go build   # 에러 없이 통과

_ = i는 변수를 읽은 것과 같은 효과를 내고, _ "fmt"는 임포트된 패키지를 “이니셜라이즈만 하고 사용은 보류"하게 만듭니다. 이 방식은 유효한 우회책이지만 임시방편이라는 점을 분명히 인지해야 합니다.

방법 B — 코드 정리 (권장)

“나중에 쓰려고” 선언한 변수는 대부분 실제로는 로직이 완성되면서 바로 사용되거나, 그대로 사라져야 합니다. 아래처럼 정리하면 에러가 없어질 뿐 아니라 코드가 더 깔끔해집니다.

1
2
3
4
5
6
7
8
package main

import "fmt"

func main() {
	i := 1
	fmt.Println(i) // 값을 실제로 사용
}
1
$ go build   # 에러 없이 통과
  • 해결책 비교
해결책장점단점적합한 상황
빈 식별자 _ = i코드 수정 최소, 에러 즉시 해소쓰지 않는 로직이 남아 혼란 유발 가능개발 중간 임시 확인·실험 코드
빈 임포트 _ "pkg"부작용(init)만 실행하고 싶을 때 필요의존성을 억지로 유지패키지 init 등록 목적
실제 사용하도록 코드 작성의도가 명확, 완성된 로직수정량이 상대적으로 큼본격 기능 구현
선언 자체 제거가장 깔끔, 컴파일 속도 유지해당 변수를 정말 안 쓸 때만 가능사용할 계획이 없는 경우
  • 실패한 해결책 (잘못된 해결책)
    • “컴파일러 플래그로 에러를 끄기” — Go는 미사용 변수·임포트에 대한 경고를 끄는 공식 플래그를 제공하지 않습니다. 이 에러는 고의적 설계이므로 끄는 방법 자체가 없습니다.
    • “선언만 남겨 두고 나중에 정리” — 위험합니다. 프로덕션에 들어가기 전 go vet과 CI 빌드가 항상 이 에러로 실패하게 되어 배포가 막힙니다. 미사용 선언은 반드시 그 자리에서 처리해야 합니다.

5. 검증 방법

수정 후 에러가 사라졌는지, 그리고 코드가 실제로 동작하는지 확인합니다.

1
2
3
go build ./...
go vet ./...
go run .
1
2
3
4
$ go build ./...
$ go vet ./...
$ go run .
# 모두 출력 없이(또는 정상 실행 결과) 종료되면 성공
  • 검증 명령 (verification_command)
    • go build ./... — 전체 패키지 컴파일 에러 확인
    • go vet ./... — 미사용·비정상 코드 정적 분석
    • go run . — 실제 실행해서 로직 동작 확인

6. 실전 적용 주의사항

  • 빈 식별자 _는 영구 해결책이 아닙니다. 특히 _ = value를 반복해서 쓰다 보면 “뭔가 쓰고 있는 것처럼 보이지만 아무것도 하지 않는” 죽은 코드가 쌓입니다. 실험 후에는 반드시 실제 로직을 완성하거나 선언을 지우세요.

  • _ "pkg"는 패키지의 init() 부작용만 필요할 때 용법이 다릅니다. 예를 들어 _ "net/http/pprof"처럼 임포트만으로 등록 효과를 내는 패턴은 정당한 사용입니다. 하지만 단순히 “나중에 쓸 예정"으로 쓰는 것은 피해야 합니다.

  • 검증 환경 및 버전별 차이: 이 동작은 Go 1.x 전체에서 일관됩니다. Go 1.0부터 현재 버전까지 미사용 변수·임포트는 계속 컴파일 에러로 처리되며, 이 점은 문서화된 공식 입장입니다. 따라서 버전을 올려도 “에러가 사라지는” 변화는 없습니다 — 오히려 go vet 등 정적 분석이 강화되는 방향으로 발전했습니다.

  • 운영 예방책 (재발 방지): 로컬 뿐 아니라 CI 파이프라인에 go build ./... && go vet ./...를 포함해 미사용 선언이 머지되는 순간 바로 잡아내도록 하세요. 그래야 개발 속도를 늦추는 “선언만 해두고 나중에 정리"가 운영 코드로 새어들어 가지 않습니다.

  • DevTrace verdict (핵심 판단): 이 문제의 핵심은 “왜 에러가 나는가"를 이해하는 데 있습니다 — Go는 미사용 변수를 버그 신호로, 미사용 임포트를 컴파일 속도 저하 요인으로 보고 둘 다 의도적으로 금지했으며, 빈 식별자 _는 임시 우회 수단일 뿐 올바른 장기 해결책은 코드 정리입니다.


출처