Go에서 rune 타입은 int32의 별칭이에요.
rune 타입은 이 int32 타입을 바탕으로 하기 때문에 부호 있는 32비트 정수 값을 저장해요.
하지만 int32 타입과는 달리, rune 타입에 저장되는 정수 값은 하나의 유니코드 문자를 나타내요.
유니코드는 ASCII의 상위 집합으로, 모든 문자에 고유한 번호를 붙여서 문자를 나타내요. 이 고유한 번호를 유니코드 코드 포인트라고 해요. 유니코드는 다양한 문자 체계와 숫자, 기호는 물론 이모지까지, 세상의 모든 문자를 유니코드 코드 포인트로 나타내는 것을 목표로 해요.
Go에서 rune 타입은 하나의 유니코드 코드 포인트를 나타내요.
다음 표에는 몇 가지 유니코드 문자와 그 문자의 유니코드 코드 포인트, 10진수 값을 정리했어요:
| 유니코드 문자 | 유니코드 코드 포인트 | 10진수 값 |
|---|---|---|
| 0 | U+0030 |
48 |
| A | U+0041 |
65 |
| a | U+0061 |
97 |
| ¿ | U+00BF |
191 |
| π | U+03C0 |
960 |
| 🧠 | U+1F9E0 |
129504 |
UTF-8은 모든 유니코드 코드 포인트를 1, 2, 3, 4바이트로 인코딩하는 가변 길이 문자 인코딩이에요.
유니코드 코드 포인트는 최대 4바이트로 인코딩될 수 있기 때문에, rune 타입은 최대 4바이트의 데이터를 담을 수 있어야 해요.
int32 타입이 최대 4바이트의 데이터를 담을 수 있기 때문에, rune 타입이 int32의 별칭인 거예요.
Go 소스 코드 파일은 UTF-8로 인코딩돼요.
rune 타입의 변수는 문자를 작은따옴표로 감싸서 선언해요:
myRune := '¿'
rune은 그저 int32의 별칭이기 때문에, 룬의 타입을 출력하면 int32가 나와요:
myRune := '¿'
fmt.Printf("myRune type: %T\n", myRune)
// Output: myRune type: int32
마찬가지로, 룬의 값을 출력하면 정수(10진수) 값이 나와요:
myRune := '¿'
fmt.Printf("myRune value: %v\n", myRune)
// Output: myRune value: 191
룬이 나타내는 유니코드 문자를 출력하려면 %c 서식 동사를 사용해요:
myRune := '¿'
fmt.Printf("myRune Unicode character: %c\n", myRune)
// Output: myRune Unicode character: ¿
룬이 나타내는 유니코드 코드 포인트를 출력하려면 %U 서식 동사를 사용해요:
myRune := '¿'
fmt.Printf("myRune Unicode code point: %U\n", myRune)
// Output: myRune Unicode code point: U+00BF
문자를 작은따옴표로 감싸서 룬을 정의하는 방법 외에도, 16진수나 10진수 숫자로 지정할 수도 있어요:
myRune := rune(0xbf)
myRune = 191
fmt.Printf("myRune Unicode character: %c\n", myRune)
// Output: myRune Unicode character: ¿
Go에서 문자열은 UTF-8로 인코딩돼요. 즉, 문자열에는 유니코드 문자가 들어 있어요. 문자열 안의 문자는 나타내는 유니코드 문자에 따라 1, 2, 3, 4바이트로 저장되고 인코딩돼요.
Go에서는 시퀀스를 나타낼 때 슬라이스를 사용하는데, 이런 슬라이스는 range를 사용해 순회할 수 있어요.
문자열을 순회하면 Go는 문자열을 일련의 룬으로 변환하는데, 각 룬은 4바이트예요(룬 타입은 int32의 별칭이라는 걸 기억해요!).
문자열이 그저 바이트의 슬라이스일 뿐이어도, range 키워드는 문자열의 바이트가 아니라 룬을 순회해요.
이 예제에서 index 변수는 현재 룬의 바이트 시퀀스가 시작하는 인덱스를 나타내고, char 변수는 현재 룬을 나타내요:
myString := "❗hello"
for index, char := range myString {
fmt.Printf("Index: %d\tCharacter: %c\t\tCode Point: %U\n", index, char, char)
}
// Output:
// Index: 0 Character: ❗ Code Point: U+2757
// Index: 3 Character: h Code Point: U+0068
// Index: 4 Character: e Code Point: U+0065
// Index: 5 Character: l Code Point: U+006C
// Index: 6 Character: l Code Point: U+006C
// Index: 7 Character: o Code Point: U+006F
룬은 1, 2, 3, 4바이트로 저장될 수 있기 때문에, 문자열의 길이가 항상 문자열의 문자 수와 같지는 않아요.
문자열의 길이를 바이트 단위로 구하려면 내장 len 함수를, 문자열의 룬 개수를 구하려면 utf8.RuneCountInString 함수를 사용해요:
import "unicode/utf8"
myString := "❗hello"
stringLength := len(myString)
numberOfRunes := utf8.RuneCountInString(myString)
fmt.Printf("myString - Length: %d - Runes: %d\n", stringLength, numberOfRunes)
// Output: myString - Length: 8 - Runes: 6
룬의 슬라이스는 문자열로 타입 변환할 수 있어요:
myRuneSlice := []rune{'e', 'x', 'e', 'r', 'c', 'i', 's', 'm'}
myString := string(myRuneSlice)
fmt.Println(myString)
// Output: exercism
마찬가지로, 문자열은 룬의 슬라이스로 타입 변환할 수 있어요. 기억하세요, 서식 동사 없이 룬을 출력하면 정수(10진수) 값이 나와요:
myString := "exercism"
myRuneSlice := []rune(myString)
fmt.Println(myRuneSlice)
// Output: [101 120 101 114 99 105 115 109]