Go 코드를 어느 정도 작성해 보았다면, Go 프로그래밍을 할 때 사용할 수 있는 코드 스타일에 제약이 있다는 것을 알 것입니다. 그중 하나로, 공백 문자가 들어갈 자리에서 줄 바꿈을 하는 것도 Go에서는 아무렇게나 할 수 없습니다. 이번 장에서는 Go에서 줄 바꿈을 할 때의 규칙에 대해서 자세히 알아보도록 하겠습니다.
{
) 전에 줄 바꿈을 하지 않는다"라는
규칙을 정하는 경우가 많습니다.
예를 들어, 다음 for
반복문 코드는 컴파일에 실패합니다.
for i := 5; i > 0; i--
{ // unexpected newline, expecting { after for clause
}
이 코드를 컴파일이 되도록 하려면, 다음과 같이 여는 중괄호가 새로운 줄에 있지 않도록 해야 합니다.
for i := 5; i > 0; i-- {
}
하지만, 위 규칙에는 예외가 있습니다. 예를 들어, 다음과 같이
for
뒤에 아무것도 붙지 않은 반복문 블록은 정상적으로 컴파일이 가능합니다.
for
{
// do something ...
}
그렇다면, Go 언어에서의 줄 바꿈에 대한 근본적인 규칙은 무엇일까요?
이 질문에 대답하기 전에 우리는 먼저 Go 코드의 모든 줄의 끝에는 원칙적으로
세미콜론(;
)이 있어야 한다는 것을 알아야 합니다.
하지만 실제 Go 코드에서 세미콜론은 거의 사용되지 않습니다. 왜일까요?
바로 대부분의 세미콜론은 필수가 아니며 생략될 수 있기 때문입니다.
이렇게 생략된 세미콜론들은 Go 컴파일러가 자동으로 추가해줍니다.
package main;
import "fmt";
func main() {
var (
i int;
sum int;
);
for i < 6 {
sum += i;
i++;
};
fmt.Println(sum);
};
위 프로그램이 semicolons.go
라는 이름의 파일에 저장되어 있을 때,
go fmt semicolons.go
명령을 실행하면 해당 파일의 모든 불필요한 세미콜론들이 삭제됩니다.
지워진 세미콜론들은 소스 코드를 컴파일하면서 자동으로 (메모리 안에서) 다시 추가됩니다.
그렇다면 세미콜론은 어떤 규칙을 따라 삽입될까요? Go 언어 명세의 해당 부분을 읽어보도록 합시다.
대부분의 Go 문법에서 공식적으로 세미콜론 ";" 을 종결자로 사용한다. Go 프로그램에서는 아래 두 가지 규칙에 따라 세미콜론 대부분을 생략할 수 있다.
첫 번째 규칙에 열거된 상황의 경우, 당연히 위의 예시와 같이 수동으로 세미콜론을 추가할 수 있습니다. 이 세미콜론들은 선택적으로 사용할 수 있다는 말이죠.
두 번째 규칙은 여러 항목을 선언한 뒤 오는 닫는 괄호 )
앞과 코드 블록이나 자료형 (struct나
interface) 선언 뒤 오는 닫는 괄호 }
앞의 세미콜론은 선택적이라는 것을 의미합니다.
이 세미콜론들이 없다면, 컴파일러가 자동으로 다시 넣어줍니다.
import (_ "math"; "fmt")
var (a int; b string)
const (M = iota; N)
type (MyInt int; T struct{x bool; y int32})
type I interface{m1(int) int; m2() string}
func f() {print("a"); panic(nil)}
생략한 세미콜론들을 컴파일러가 자동으로 넣어준 뒤에는 다음과 같은 코드가 됩니다.
import (_ "math"; "fmt";);
var (a int; b string;);
const (M = iota; N;);
type (MyInt int; T struct{x bool; y int32;};);
type I interface{m1(int) int; m2() string;};
func f() {print("a"); panic(nil);};
이 두 가지 조건을 제외하고 컴파일러가 세미콜론을 넣어주는 경우는 없습니다. 다른 상황에서는 우리가 직접 필요한 세미콜론을 넣어주어야 하죠. 예를 들어, 위의 예시 코드에서 각 줄의 첫 번째 세미콜론들은 모두 필요합니다. 아래 예시의 세미콜론들 역시 모두 필수로 넣어주어야 하죠.
var a = 1; var b = true
a++; b = !b
print(a); print(b)
세미콜론 삽입 규칙을 다시 살펴보면 우리는 for
키워드 바로 뒤에는 절대
자동으로 세미콜론이 추가되지 않을 것임을 알 수 있습니다.
위의 for
반복문 뒤에 아무것도 붙지 않았던 예시 코드가 유효한 이유죠.
func f() {
a := 0
// 아래 두 줄 모두 컴파일에 실패합니다.
println(a++) // unexpected ++, expecting comma or )
println(a--) // unexpected --, expecting comma or )
}
컴파일러는 위의 코드를 아래와 같이 볼 것이기 때문이죠.
func f() {
a := 0;
println(a++;);
println(a--;);
}
또한 선택자(selector)
.
앞에서 줄을 바꿀 수 없는 것도
세미콜론 추가 규칙 때문입니다.
아래 코드와 같이 선택자 뒤에서만 줄 바꿈이 가능하고,
anObject.
MethodA().
MethodB().
MethodC()
선택자 앞에서 줄 바꿈을 한 아래 코드는 컴파일이 되지 않습니다.
anObject
.MethodA()
.MethodB()
.MethodC()
두 번째 코드를 컴파일러가 읽으면 규칙에 따라 줄 끝마다 세미콜론을 붙일 것이고,
그렇게 나온 다음과 같은 코드는 당연히 유효하지 않기 때문입니다.
anObject;
.MethodA();
.MethodB();
.MethodC();
이러한 세미콜론 삽입 규칙은 더 깔끔한 코드를 짜게 도와줍니다. 하지만 가끔은 유효하지만 조금 이상한 코드 역시 쓸 수 있게 해 주죠. 예를 들어봅시다.
package main
import "fmt"
func alwaysFalse() bool {return false}
func main() {
for
i := 0
i < 6
i++ {
// use i ...
}
if x := alwaysFalse()
!x {
// do something ...
}
switch alwaysFalse()
{
case true: fmt.Println("true")
case false: fmt.Println("false")
}
}
위 예시에 나와 있는 세 개의 제어 흐름(control flow) 블록은 모두 유효합니다. 컴파일러가 9, 10, 15, 20번째 줄 끝에 세미콜론을 추가하기 때문이죠.
여기서 주목할 부분은 위 예시의switch-case
블록은
false
가 아닌 true
를 출력한다는 점입니다.
아래의 코드와는 정반대의 결과죠.
switch alwaysFalse() {
case true: fmt.Println("true")
case false: fmt.Println("false")
}
먼저 보았던 줄 바꿈이 들어가 있는 코드를 go fmt
명령으로 정규화하면
alwaysFalse()
함수 호출 뒤에 세미콜론이 다음과 같이 삽입되게 됩니다.
switch alwaysFalse();
{
case true: fmt.Println("true")
case false: fmt.Println("false")
}
이는 다음 코드와 동치이므로, true
를 출력하게 되는 것이죠.
switch alwaysFalse(); true {
case true: fmt.Println("true")
case false: fmt.Println("false")
}
따라서 가끔은 go fmt
과 go vet
을 실행해서 코드를 점검해 보는 것이 좋습니다.
func f(x int) {
switch x {
case 1:
{
goto A
A: // 정상적으로 컴파일됨
}
case 2:
goto B
B: // syntax error: missing statement after label
case 0:
goto C
C: // 정상적으로 컴파일됨
}
}
컴파일 중 출력된 오류 메시지를 보면 라벨의 선언 뒤에는 구문(statement)이 따라와야 한다고 합니다. 하지만 위 코드에서 선언된 세 라벨 모두 뒤에 구문이 있어 보이지는 않는데요. 왜
B:
의 선언만 유효하지 않은 걸까요?
바로 아래와 같이 두 번째 규칙에 따라 컴파일러가 A:
와 C:
뒤에 있는 }
앞에 세미콜론을 넣기 때문입니다.
func f(x int) {
switch x {
case 1:
{
goto A
A:
;} // 세미콜론 삽입됨
case 2:
goto B
B: // syntax error: missing statement after label
case 0:
goto C
C:
;} // 세미콜론 삽입됨
}
단독으로 쓰인 세미콜론은 빈 구문을 구성하는데,
이 또한 하나의 구문이므로 그 앞의 A:
와 C:
의 선언 역시 유효하게 되는 것입니다.
반면 B:
의 선언 뒤에는 구문이 아닌 case 0:
가 있으므로
B:
의 선언은 유효하지 않은 것이죠.
B:
라벨 선언 뒤에 세미콜론(빈 구문)을 추가하면 정상적으로 컴파일이 되는 것을 볼 수 있습니다.
,
)는 자동으로 추가되지 않는다합성 리터럴이나 함수의 전달인자·매개변수·반환값 목록과 같이 비슷한 항목들을 여러 개 포함하는 몇몇 구문들은 그 항목들을 구분하기 위해 쉼표를 사용합니다. 이때 마지막 항목 뒤에는 항상 쉼표가 따라올 수 있는데요. 만약 이 쉼표가 줄을 바꾸기 전 마지막 문자라면 필수이지만, 그렇지 않은 때는 생략해도 무방합니다. 세미콜론과는 달리 그 어떤 경우에도 컴파일러가 자동으로 쉼표를 넣는 일은 없습니다.
예를 들어, 다음은 유효한 Go 코드입니다.func f1(a int, b string,) (x bool, y int,) {
return true, 789
}
var f2 func (a int, b string) (x bool, y int)
var f3 func (a int, b string, // 마지막 쉼표가 필요합니다
) (x bool, y int, // 마지막 쉼표가 필요합니다
)
var _ = []int{2, 3, 5, 7, 9,} // 마지막 쉼표를 생략할 수 있습니다
var _ = []int{2, 3, 5, 7, 9, // 마지막 쉼표가 필요합니다
}
var _ = []int{2, 3, 5, 7, 9}
var _, _ = f1(123, "Go",) // 마지막 쉼표를 생략할 수 있습니다
var _, _ = f1(123, "Go", // 마지막 쉼표가 필요합니다
)
var _, _ = f1(123, "Go")
// The same for explicit conversions.
var _ = string(65,) // 마지막 쉼표를 생략할 수 있습니다
var _ = string(65, // 마지막 쉼표가 필요합니다
)
하지만 다음의 코드는 유효하지 않습니다.
컴파일러가 두 번째 줄을 제외한 나머지 줄에는 모두 끝에 세미콜론을 삽입할 것이기 때문이죠.
총 세 개의 줄에서 unexpected newline
구문 오류가 발생할 겁니다.
func f1(a int, b string,) (x bool, y int // error
) {
return true, 789
}
var _ = []int{2, 3, 5, 7 // error: unexpected newline
}
var _, _ = f1(123, "Go" // error: unexpected newline
)
마치며, 위의 설명을 생각하며 Go에서의 줄 바꿈 규칙을 정리해봅시다.
break
, continue
, return
을 제외한 키워드가 올 때,
내지는 위의 세 키워드가 오더라도 그 바로 뒤에 라벨을 선언하거나 결과를 반환하지 않을 때.
다른 여러 Go의 디자인과 마찬가지로, 세미콜론 삽입 규칙은 찬사와 비판을 동시에 받고 있습니다. 어떤 프로그래머들은 이 규칙이 코드 스타일의 자유를 제한한다고 생각해 마음에 들어 하지 않기도 합니다. 반면 좋아하는 사람들은 이 규칙 덕분에 컴파일이 더 빨라질 뿐 아니라, 여러 프로그래머가 작성한 코드가 보기에 비슷해져 서로의 코드를 더 쉽게 이해할 수 있게 해준다고 생각합니다.
The Go 101 프로젝트는 Github 에서 호스팅됩니다. 오타, 문법 오류, 부정확한 표현, 설명 결함, 코드 버그, 끊어진 링크와 같은 모든 종류의 실수에 대한 수정 사항을 제출하여 Go 101을 개선을 돕는 것은 언제나 환영합니다.
주기적으로 Go에 대한 깊이 있는 정보를 얻고 싶다면 Go 101의 공식 트위터 계정인 @go100and1을 팔로우하거나 Go 101 슬랙 채널에j가입해주세요.
reflect
표준 패키지sync
표준 패키지sync/atomic
표준 패키지