fmt 패키지에는 출력 함수 계열이 세 가지 있고, 계열마다 같은 세 가지 변형이 있습니다:
| 함수 | 출력 대상 | 형식 |
|---|---|---|
Print, Println, Printf | 표준 출력 | 기본, 공백과 줄바꿈을 붙인 기본, 형식 문자열 |
Sprint, Sprintln, Sprintf | 반환되는 string | 같은 세 가지 스타일 |
Fprint, Fprintln, Fprintf | 모든 io.Writer(파일, 버퍼, HTTP 응답) | 같은 세 가지 스타일 |
Errorf | 반환되는 error | 형식 문자열, 감싸기용 %w 추가 |
Println은 피연산자 사이에 공백을, 끝에 줄바꿈을 붙입니다. Printf는 아무것도 붙이지 않으므로 \n을 직접 써야 합니다. Print는 둘 다 문자열이 아닌 피연산자 사이에만 공백을 넣는데, 이것이 꽤 헷갈리기 때문에 대부분의 코드는 Println이나 Printf를 씁니다.
포맷 동사
일반
| 동사 | 출력 | 출력 예시 |
|---|---|---|
%v | 기본 형식의 값 | {Ana 31 [admin]} |
%+v | 필드 이름을 포함한 구조체 | {Name:Ana Age:31 Tags:[admin]} |
%#v | 값의 Go 문법 표현 | main.User{Name:"Ana", Age:31, Tags:[]string{"admin"}} |
%T | 타입 | main.User |
%% | 퍼센트 기호 그대로 | % |
디버깅할 때 쓸 것은 %+v입니다. 구조체 포인터는 주소가 아니라 &{...}로 출력됩니다. 맵은 키를 정렬해서 출력하므로, 맵 순회 순서가 무작위여도 출력은 안정적입니다.
정수
| 동사 | 의미 | fmt.Sprintf(verb, 255) |
|---|---|---|
%d | 10진수 | 255 |
%b | 2진수 | 11111111 |
%o | 8진수 | 377 |
%O | 0o 접두사가 붙은 8진수 | 0o377 |
%x / %X | 16진수, 소문자 또는 대문자 | ff / FF |
%#x | 0x 접두사가 붙은 16진수 | 0xff |
%c | 그 코드 포인트의 문자 | ÿ |
%q | 따옴표로 감싼 문자 리터럴 | 'ÿ' |
%U | 유니코드 형식 | U+00FF |
실수
| 동사 | 의미 | fmt.Sprintf(verb, 1234.5678) |
|---|---|---|
%f | 10진수, 기본 소수점 6자리 | 1234.567800 |
%.2f | 10진수, 소수점 2자리 | 1234.57 |
%e | 과학적 표기법 | 1.234568e+03 |
%g | %e와 %f 중 짧은 쪽, 뒤쪽 0 없음 | 1234.5678 |
%v | %g와 같음 | 1234.5678 |
%.2f는 실수의 정확한 이진 값을 반올림하므로 fmt.Sprintf("%.2f", 2.675)는 2.67입니다. 2.675에 가장 가까운 float64가 그보다 조금 작기 때문입니다. 돈을 실수로 형식화하지 마세요. 센트를 정수로 보관하세요.
문자열과 바이트
| 동사 | 의미 | fmt.Sprintf(verb, "go\n") |
|---|---|---|
%s | 문자열 그대로 | go와 줄바꿈 |
%q | 큰따옴표로 감싸고 이스케이프를 보여 줌 | "go\n" |
%x | 각 바이트의 16진수 | 676f0a |
% x | 공백을 넣은 16진수 | 67 6f 0a |
[]byte에 %s를 쓰면 텍스트로 출력되고, %v를 쓰면 숫자([104 105])로 출력됩니다.
그 밖의 타입
| 동사 | 타입 | 출력 |
|---|---|---|
%t | bool | true 또는 false |
%p | 포인터, 슬라이스, 맵, 채널, 함수 | 0xc000012345 같은 주소 |
%w | error (Errorf에서만) | 오류 메시지, 그리고 감싸기 |
너비, 정밀도, 채우기
%와 동사 사이에 플래그, 너비, 정밀도를 넣을 수 있습니다:
| 형태 | 효과 |
|---|---|
%5d | 너비 5, 오른쪽 정렬(왼쪽을 공백으로 채움) |
%-5d | 너비 5, 왼쪽 정렬 |
%05d | 0으로 채움 |
%.2f | 소수점 아래 2자리 |
%8.2f | 너비 8, 소수점 2자리 |
%.3s | 문자열에서 최대 3글자 |
%+d | 부호를 항상 표시 |
%*d | 다음 인자에서 너비를 가져옴 |
2.5에 %.0f를 쓰면 2가 출력된다는 점에 주의하세요. 여기서 Go는 오사오입(가장 가까운 짝수로 반올림)을 합니다. 문자열의 너비는 화면 칸이 아니라 룬 개수를 세므로, 한글, 한자, 이모지가 섞이면 표가 여전히 어긋날 수 있습니다. 길이가 제각각인 텍스트로 열을 맞추려면 text/tabwriter가 측정을 대신해 줍니다.
인자 인덱스
%[n]은 위치로 인자를 골라서 재사용할 수 있게 합니다:
fmt.Printf("%[2]s %[1]s\n", "world", "hello") // hello world
fmt.Printf("%d %[1]x %[1]b\n", 10) // 10 a 1010
Errorf와 %w
fmt.Errorf는 형식 문자열로 error를 만듭니다. %w를 쓰면 다른 오류도 감싸므로, 호출자가 여전히 원래 오류를 감지할 수 있습니다:
호출자가 원인을 확인해야 할 수 있다면 %w를, 일부러 숨길 때는 %v를 쓰세요. Go 1.20부터 Errorf 호출 하나에 %w 동사를 여러 개 넣을 수 있습니다. 감싸기는 오류 처리 페이지에서 깊이 다룹니다.
String()으로 직접 형식 정하기
String() string 메서드가 있는 타입은 %v, %s, Println이 자신을 보여 주는 방식을 직접 정합니다:
%d는 String()을 거치지 않고 기반 숫자를 출력합니다. 오류 타입에서 그에 해당하는 메서드는 Error() string이며, String()보다 우선합니다.
동사가 틀렸을 때
fmt는 잘못된 형식에 절대 패닉을 일으키지 않습니다. 문제를 그 자리에 출력합니다:
fmt.Printf("%d\n", "oops")
fmt.Printf("%d %d\n", 1)
fmt.Printf("%d\n", 1, 2)
%!d(string=oops)
1 %!d(MISSING)
1
%!(EXTRA int=2)
아무것도 죽이지 않기 때문에 이런 출력은 운영 환경까지 가기 쉽습니다. go vet은 빌드할 때 셋 모두 잡아 줍니다:
./main.go:8:2: fmt.Printf format %d has arg "oops" of wrong type string
./main.go:9:2: fmt.Printf format %d reads arg #2, but call has 1 arg
./main.go:10:2: fmt.Printf call needs 1 arg but has 2 args
성능 참고
fmt는 모든 인자를 any로 받아 런타임에 타입을 살펴보므로(구조체, 슬라이스, 맵은 리플렉션으로 처리), 로깅과 출력에는 괜찮지만 빡빡한 반복문에서는 비용이 눈에 띕니다. 숫자 하나를 변환할 때는 strconv.Itoa와 strconv.FormatFloat가 Sprintf보다 빠릅니다. 반복문에서 긴 문자열을 만들 때는 Sprintf 결과를 이어 붙이지 말고 fmt.Fprintf(&b, ...)로 strings.Builder에 쓰세요.
자주 묻는 질문
Go에서 Println, Printf, Sprintf의 차이는 무엇인가요?
fmt.Println은 인자를 공백으로 구분해서 출력하고 끝에 줄바꿈을 붙입니다. fmt.Printf는 형식 문자열에 따라 출력하며 줄바꿈을 붙이지 않습니다. fmt.Sprintf는 Printf와 같은 방식으로 형식화하지만 출력하지 않고 결과를 문자열로 반환합니다. fmt.Errorf도 같은 일을 하고 error를 반환합니다.
Go에서 구조체를 필드 이름과 함께 출력하려면 어떻게 하나요?
%+v를 씁니다: fmt.Printf("%+v\n", user)는 {Name:Ana Age:31}을 출력합니다. %v는 값만 {Ana 31}로 출력하고, %#v는 타입을 포함한 Go 문법 main.User{Name:"Ana", Age:31}로 출력합니다.
Go에서 실수를 소수점 둘째 자리까지 형식화하려면 어떻게 하나요?
%.2f를 씁니다: fmt.Sprintf("%.2f", 3.14159)는 "3.14"를 반환합니다. 열을 맞추려면 너비를 추가하고(%8.2f), 왼쪽 정렬하려면 빼기 기호를 붙입니다(%-8.2f). strconv.FormatFloat(f, 'f', 2, 64)는 형식 문자열 없이 같은 결과를 줍니다.
fmt.Errorf에서 %w는 무엇을 하나요?
%w는 오류를 %v처럼 형식화하면서 감싸기도 하므로, 새 오류가 원래 오류를 담게 됩니다. 그러면 errors.Is와 errors.As가 감싼 오류를 찾을 수 있습니다: err := fmt.Errorf("load config: %w", os.ErrNotExist)이면 errors.Is(err, os.ErrNotExist)가 참입니다. %w는 fmt.Errorf에서만 동작합니다.
출력에 %!d(string=...)가 나오는 이유는 무엇인가요?
동사가 인자의 타입과 맞지 않기 때문입니다. 예를 들어 %d에 문자열을 넘긴 경우입니다. fmt는 패닉 대신 문제를 그 자리에 출력합니다: %!d(string=oops). 인자가 모자라면 %!d(MISSING), 남으면 %!(EXTRA int=2)가 출력됩니다. go vet은 프로그램을 실행하기 전에 셋 모두 잡아냅니다.