토큰
토큰 (Tokens)
소스 코드는 컴파일러가 가장 먼저 작은 조각들로 쪼개는데, 그 조각 하나하나를 토큰(token) 이라고 불러요. Rust의 문법을 구성하는 기초 단위죠. 어떤 종류의 토큰이 있고, 리터럴은 어떻게 생겼는지 정리해 볼게요.
출처: Rust Reference
본문
문법 (Syntax)
Token → RESERVED_TOKEN
| RAW_IDENTIFIER
| CHAR_LITERAL
| STRING_LITERAL
| RAW_STRING_LITERAL
| BYTE_LITERAL
| BYTE_STRING_LITERAL
| RAW_BYTE_STRING_LITERAL
| C_STRING_LITERAL
| RAW_C_STRING_LITERAL
| FLOAT_LITERAL
| INTEGER_LITERAL
| LIFETIME_TOKEN
| PUNCTUATION
| IDENTIFIER_OR_KEYWORD
토큰은 정규(비재귀) 언어로 정의되는 문법의 원시 생성물(primitive production)이에요. Rust 소스 입력은 다음 종류의 토큰으로 쪼개져요.
이 문서의 문법에서 "단순한" 토큰은 문자열 표 생성물(string table production) 형태로 주어지고, monospace 글꼴로 나타나요.
리터럴 (Literals)
리터럴은 리터럴 표현식에 쓰이는 토큰이에요.
문자와 문자열 (Characters and strings)
| 종류 | 예시 | # 개수[1] |
허용 문자 | 이스케이프 |
|---|---|---|---|---|
| 문자 | 'H' |
0 | 모든 유니코드 | Quote & ASCII & Unicode |
| 문자열 | "hello" |
0 | 모든 유니코드 | Quote & ASCII & Unicode |
| Raw 문자열 | r#"hello"# |
<256 | 모든 유니코드 | N/A |
| 바이트 | b'H' |
0 | 모든 ASCII | Quote & Byte |
| 바이트 문자열 | b"hello" |
0 | 모든 ASCII | Quote & Byte |
| Raw 바이트 문자열 | br#"hello"# |
<256 | 모든 ASCII | N/A |
| C 문자열 | c"hello" |
0 | 모든 유니코드 | Quote & Byte & Unicode |
| Raw C 문자열 | cr#"hello"# |
<256 | 모든 유니코드 | N/A |
ASCII 이스케이프:
| 이름 | |
|---|---|
\x41 |
7비트 문자 코드 (정확히 2자리 16진수, 최대 0x7F) |
\n |
개행 (Newline) |
\r |
캐리지 리턴 (Carriage return) |
\t |
탭 (Tab) |
\\ |
백슬래시 (Backslash) |
\0 |
널 (Null) |
바이트 이스케이프:
| 이름 | |
|---|---|
\x7F |
8비트 문자 코드 (정확히 2자리 16진수) |
\n |
개행 (Newline) |
\r |
캐리지 리턴 (Carriage return) |
\t |
탭 (Tab) |
\\ |
백슬래시 (Backslash) |
\0 |
널 (Null) |
유니코드 이스케이프:
| 이름 | |
|---|---|
\u{7FFF} |
24비트 유니코드 문자 코드 (최대 6자리 16진수) |
따옴표 이스케이프:
| 이름 | |
|---|---|
\' |
작은따옴표 (Single quote) |
\" |
큰따옴표 (Double quote) |
숫자 (Numbers):
| [숫자 리터럴][2] | 예시 | 지수 (Exponentiation) |
|---|---|---|
| 십진수 정수 | 98_222 |
N/A |
| 16진수 정수 | 0xff |
N/A |
| 8진수 정수 | 0o77 |
N/A |
| 2진수 정수 | 0b1111_0000 |
N/A |
| 부동소수점 | 123.0E+77 |
Optional |
접미사 (Suffixes)
접미사(suffix)는 리터럴의 본체(primary part) 뒤에 공백 없이 따라오는, non-raw 식별자나 키워드와 같은 형태의 문자 시퀀스예요.
SUFFIX → _ ^ XID_Continue+
| XID_Start XID_Continue*
어떤 종류의 리터럴(문자열, 정수 등)이든 어떤 접미사를 붙여도 토큰으로는 유효해요. 접미사가 달린 리터럴 토큰은 오류 없이 매크로에 전달될 수 있어요. 매크로가 그런 토큰을 어떻게 해석할지, 오류를 낼지 말지를 스스로 정해요. 특히, by-example 매크로의 literal 프래그먼트 지정자는 임의의 접미사가 붙은 리터럴 토큰을 매치해요.
#![allow(unused)]
fn main() {
macro_rules! blackhole { ($tt:tt) => () }
macro_rules! blackhole_lit { ($l:literal) => () }
blackhole!("string"suffix); // OK
blackhole_lit!(1suffix); // OK
}
하지만 리터럴 표현식이나 패턴으로 해석되는 리터럴 토큰의 접미사는 제한돼요. 숫자가 아닌 리터럴 토큰에서는 어떤 접미사도 거부되고, 숫자 리터럴 토큰은 아래 목록의 접미사만 허용돼요.
| 정수 | 부동소수점 |
|---|---|
u8, i8, u16, i16, u32, i32, u64, i64, u128, i128, usize, isize |
f32, f64 |
문자 리터럴 (Character literals)
CHAR_LITERAL → ' ( ~[' \ LF CR TAB] | QUOTE_ESCAPE | ASCII_ESCAPE | UNICODE_ESCAPE ) ' SUFFIX?
QUOTE_ESCAPE → \' | \"
ASCII_ESCAPE → \x OCT_DIGIT HEX_DIGIT
| \n | \r | \t | \\ | \0
UNICODE_ESCAPE → \u{ ( HEX_DIGIT _* )1..=6 valid hex char value }
문자 리터럴은 두 개의 U+0027(작은따옴표)로 둘러싸인 단일 유니코드 문자예요. 단 U+0027 자체는 앞에 U+005C(\)를 붙여 이스케이프해야 해요.
문자열 리터럴 (String literals)
STRING_LITERAL → " ( ~[" \ CR]
| QUOTE_ESCAPE
| ASCII_ESCAPE
| UNICODE_ESCAPE
| STRING_CONTINUE
)* " SUFFIX?
STRING_CONTINUE → \ LF
문자열 리터럴은 두 개의 U+0022(큰따옴표)로 둘러싸인 유니코드 문자들의 시퀀스예요. 단 U+0022 자체는 앞에 U+005C(\)를 붙여 이스케이프해야 해요.
문자열 리터럴에는 U+000A(LF)로 표현되는 줄바꿈이 허용돼요. U+000D(CR)는 문자열 리터럴에 나타날 수 없어요. 이스케이프되지 않은 U+005C(\)가 줄바꿈 바로 앞에 오면, 그 줄바꿈은 토큰이 나타내는 문자열에 포함되지 않아요. 문자열 연속 이스케이프에서 자세히 봐요.
문자 이스케이프 (Character escapes)
문자나 non-raw 문자열 리터럴에서 쓸 수 있는 추가 이스케이프들이 있어요. 이스케이프는 U+005C(\)로 시작해 다음 형태 중 하나로 이어져요.
- 7비트 코드 포인트 이스케이프:
U+0078(x)로 시작하고 정확히 두 자리 16진수(값이0x7F이하)가 이어져요. 주어진 16진수 값과 같은 값을 가진 ASCII 문자를 나타내요. 더 높은 값은 유니코드 코드 포인트인지 바이트 값인지 모호하기 때문에 허용되지 않아요. - 24비트 코드 포인트 이스케이프:
U+0075(u)로 시작하고U+007B({)와U+007D(})로 둘러싸인 최대 여섯 자리 16진수가 이어져요. 주어진 16진수 값과 같은 유니코드 코드 포인트를 나타내요. 값은 유효한 유니코드 스칼라 값이어야 해요. - 공백 이스케이프:
n,r,t중 하나로, 각각 유니코드 값U+000A(LF),U+000D(CR),U+0009(HT)를 나타내요. - 널 이스케이프:
0문자로, 유니코드 값U+0000(NUL)을 나타내요. - 백슬래시 이스케이프:
\문자로, 자기 자신을 나타내기 위해 이스케이프해야 해요.
Raw 문자열 리터럴 (Raw string literals)
RAW_STRING_LITERAL → r " ^ RAW_STRING_CONTENT " SUFFIX?
| r #n:1..=255 ^ " RAW_STRING_CONTENT_HASHED " #n SUFFIX?
RAW_STRING_CONTENT → ( !" ~[CR] )*
RAW_STRING_CONTENT_HASHED → ( !( " #n ) ~[CR] )*
Raw 문자열 리터럴은 어떤 이스케이프도 처리하지 않아요. U+0072(r)로 시작하고, 256개 미만의 U+0023(#)과 U+0022(큰따옴표)가 이어져요.
raw 문자열 본문은 U+000D(CR)를 제외한 어떤 유니코드 문자 시퀀스든 담을 수 있어요. 종료는 여는 U+0022(큰따옴표) 앞에 있던 것과 같은 개수의 U+0023(#) 이 뒤따르는 또 다른 U+0022(큰따옴표)로만 일어나요.
raw 문자열 본문에 담긴 모든 유니코드 문자는 자기 자신을 나타내요. U+0022(큰따옴표, 여는 데 쓴 것보다 많은 U+0023이 뒤따르는 경우 제외)나 U+005C(\)는 특별한 의미가 없어요.
문자열 리터럴 예시를 볼게요.
#![allow(unused)]
fn main() {
"foo"; r"foo"; // foo
"\"foo\""; r#""foo""#; // "foo"
"foo #\"# bar";
r##"foo #"# bar"##; // foo #"# bar
"\x52"; "R"; r"R"; // R
"\\x52"; r"\x52"; // \x52
}
바이트 리터럴 (Byte literals)
BYTE_LITERAL → b' ^ ( ASCII_FOR_CHAR | BYTE_ESCAPE ) ' SUFFIX?
ASCII_FOR_CHAR → ![' \ LF CR TAB] ASCII
BYTE_ESCAPE → \x HEX_DIGIT HEX_DIGIT
| \n | \r | \t | \\ | \0 | \' | \"
바이트 리터럴은 U+0062(b)와 U+0027(작은따옴표)이 앞에 오고 U+0027(작은따옴표)으로 끝나는 단일 ASCII 문자(U+0000 ~ U+007F 범위) 또는 단일 이스케이프예요. 리터럴 안에 U+0027이 있으면 앞에 U+005C(\)를 붙여 이스케이프해야 해요. 바이트 리터럴은 u8 부호 없는 8비트 정수 숫자 리터럴과 동등해요.
바이트 문자열 리터럴 (Byte string literals)
BYTE_STRING_LITERAL → b" ^ ( ASCII_FOR_STRING | BYTE_ESCAPE | STRING_CONTINUE )* " SUFFIX?
ASCII_FOR_STRING → ![" \ CR] ASCII
non-raw 바이트 문자열 리터럴은 U+0062(b)와 U+0022(큰따옴표)가 앞에 오고 U+0022(큰따옴표)로 끝나는 ASCII 문자와 이스케이프의 시퀀스예요. 리터럴 안에 U+0022가 있으면 앞에 U+005C(\)를 붙여 이스케이프해야 해요.
바이트 문자열 리터럴에는 U+000A(LF)로 표현되는 줄바꿈이 허용돼요. U+000D(CR)는 나타날 수 없어요. 이스케이프되지 않은 \가 줄바꿈 바로 앞에 오면 그 줄바꿈은 토큰이 나타내는 문자열에 포함되지 않아요.
바이트나 non-raw 바이트 문자열 리터럴에서 쓸 수 있는 추가 이스케이프에요.
- 바이트 이스케이프:
x로 시작하고 정확히 두 자리 16진수가 이어져요. 주어진 16진수 값과 같은 바이트를 나타내요. - 공백 이스케이프:
n,r,t중 하나로, 각각 바이트 값0x0A(ASCII LF),0x0D(ASCII CR),0x09(ASCII HT)를 나타내요. - 널 이스케이프:
0문자로, 바이트 값0x00(ASCII NUL)을 나타내요. - 백슬래시 이스케이프:
\문자로, 자신의 ASCII 인코딩0x5C를 나타내기 위해 이스케이프해야 해요.
Raw 바이트 문자열 리터럴 (Raw byte string literals)
RAW_BYTE_STRING_LITERAL → br " ^ RAW_BYTE_STRING_CONTENT " SUFFIX?
| br #n:1..=255 ^ " RAW_BYTE_STRING_CONTENT_HASHED " #n SUFFIX?
RAW_BYTE_STRING_CONTENT → ( !" ASCII_FOR_RAW )*
RAW_BYTE_STRING_CONTENT_HASHED → ( !( " #n ) ASCII_FOR_RAW )*
ASCII_FOR_RAW → ![CR] ASCII
Raw 바이트 문자열 리터럴은 어떤 이스케이프도 처리하지 않아요. U+0062(b), 이어서 U+0072(r), 이어서 256개 미만의 U+0023(#), 그리고 U+0022(큰따옴표)로 시작해요.
raw 문자열 본문은 U+000D(CR)를 제외한 어떤 ASCII 문자 시퀀스든 담을 수 있어요. 종료는 여는 큰따옴표 앞의 것과 같은 개수의 #이 뒤따르는 또 다른 큰따옴표로만 일어나요. raw 바이트 문자열 리터럴은 non-ASCII 바이트를 담을 수 없어요.
본문의 모든 문자는 자신의 ASCII 인코딩을 나타내며, "(여는 데 쓴 것보다 많은 #이 뒤따르는 경우 제외)이나 \는 특별한 의미가 없어요.
바이트 문자열 리터럴 예시를 볼게요.
#![allow(unused)]
fn main() {
b"foo"; br"foo"; // foo
b"\"foo\""; br#""foo""#; // "foo"
b"foo #\"# bar";
br##"foo #"# bar"##; // foo #"# bar
b"\x52"; b"R"; br"R"; // R
b"\\x52"; br"\x52"; // \x52
}
C 문자열 리터럴 (C string literals)
C_STRING_LITERAL → c" ^ (
~[" \ CR NUL]
| BYTE_ESCAPE except `\0` or `\x00`
| UNICODE_ESCAPE except `\u{0}`, `\u{00}`, …, `\u{000000}`
| STRING_CONTINUE
)* " SUFFIX?
C 문자열 리터럴은 U+0063(c)와 U+0022(큰따옴표)가 앞에 오고 U+0022로 끝나는 유니코드 문자와 이스케이프의 시퀀스예요. 리터럴 안에 U+0022가 있으면 앞에 \를 붙여 이스케이프해야 해요.
C 문자열은 바이트 0x00으로 암묵적으로 종료돼요. 그래서 C 문자열 리터럴 c"" 은 바이트 문자열 리터럴 b"\x00" 로 &CStr를 직접 만드는 것과 동등해요. 암묵적 종결자 외에는 바이트 0x00이 C 문자열 안에 허용되지 않아요.
줄바꿈(LF)은 허용되고 CR은 허용되지 않아요. 이스케이프되지 않은 \가 줄바꿈 바로 앞에 오면 그 줄바꿈은 포함되지 않아요.
non-raw C 문자열 리터럴에서 쓸 수 있는 추가 이스케이프에요.
- 바이트 이스케이프:
x로 시작하고 정확히 두 자리 16진수가 이어져요. 주어진 16진수 값과 같은 바이트를 나타내요. - 24비트 코드 포인트 이스케이프:
u로 시작하고 중괄호로 둘러싸인 최대 여섯 자리 16진수가 이어져요. 주어진 값과 같은 유니코드 코드 포인트를 UTF-8로 인코딩해 나타내요. - 공백 이스케이프:
n,r,t중 하나로, 각각 바이트 값0x0A(LF),0x0D(CR),0x09(HT)를 나타내요. - 백슬래시 이스케이프:
\문자로, 자신의 ASCII 인코딩0x5C를 나타내기 위해 이스케이프해야 해요.
C 문자열은 정의된 인코딩 없이 바이트를 나타내지만, C 문자열 리터럴은 U+007F보다 큰 유니코드 문자를 담을 수 있어요. 그런 문자는 그 문자의 UTF-8 표현 바이트로 대체돼요.
다음 C 문자열 리터럴들은 서로 동등해요.
#![allow(unused)]
fn main() {
c"æ"; // LATIN SMALL LETTER AE (U+00E6)
c"\u{00E6}";
c"\xC3\xA6";
}
2021 에디션 차이: C 문자열 리터럴은 2021 에디션 이상에서 받아들여져요. 이전 에디션에서는
c""토큰이c ""로 렉싱돼요.
Raw C 문자열 리터럴 (Raw C string literals)
RAW_C_STRING_LITERAL → cr " ^ RAW_C_STRING_CONTENT " SUFFIX?
| cr #n:1..=255 ^ " RAW_C_STRING_CONTENT_HASHED " #n SUFFIX?
RAW_C_STRING_CONTENT → ( !" ~[CR NUL] )*
RAW_C_STRING_CONTENT_HASHED → ( !( " #n ) ~[CR NUL] )*
Raw C 문자열 리터럴은 어떤 이스케이프도 처리하지 않아요. U+0063(c), 이어서 U+0072(r), 256개 미만의 #, 그리고 큰따옴표로 시작해요.
raw C 문자열 본문은 U+0000(NUL)과 U+000D(CR)를 제외한 어떤 유니코드 문자 시퀀스든 담을 수 있어요. 종료는 여는 큰따옴표 앞의 것과 같은 개수의 #이 뒤따르는 또 다른 큰따옴표로만 일어나요.
본문의 모든 문자는 UTF-8 인코딩으로 자기 자신을 나타내며, "(여는 데 쓴 것보다 많은 #이 뒤따르는 경우 제외)이나 \는 특별한 의미가 없어요.
2021 에디션 차이: Raw C 문자열 리터럴은 2021 에디션 이상에서 받아들여져요. 이전 에디션에서는
cr""가cr ""로,cr#""#가 비문법적(non-grammatical)인cr #""#로 렉싱돼요.
C 문자열과 raw C 문자열 리터럴 예시를 볼게요.
#![allow(unused)]
fn main() {
c"foo"; cr"foo"; // foo
c"\"foo\""; cr#""foo""#; // "foo"
c"foo #\"# bar";
cr##"foo #"# bar"##; // foo #"# bar
c"\x52"; c"R"; cr"R"; // R
c"\\x52"; cr"\x52"; // \x52
}
숫자 리터럴 (Number literals)
숫자 리터럴은 정수 리터럴이거나 부동소수점 리터럴이에요. 두 종류를 인식하는 문법은 섞여 있어요.
정수 리터럴 (Integer literals)
INTEGER_LITERAL → ( BIN_LITERAL | OCT_LITERAL | HEX_LITERAL | DEC_LITERAL )
^ ![RESERVED_FLOAT] SUFFIX?
DEC_LITERAL → DEC_DIGIT ( DEC_DIGIT | _ )*
BIN_LITERAL → 0b ^ _* BIN_DIGIT ( BIN_DIGIT | _ )* ![e E 2-9]
OCT_LITERAL → 0o ^ _* OCT_DIGIT ( OCT_DIGIT | _ )* ![e E 8-9]
HEX_LITERAL → 0x ^ _* HEX_DIGIT ( HEX_DIGIT | _ )*
BIN_DIGIT → [0-1]
OCT_DIGIT → [0-7]
DEC_DIGIT → [0-9]
HEX_DIGIT → [0-9 a-f A-F]
RESERVED_FLOAT → . !( . | _ | XID_Start )
정수 리터럴은 네 가지 형태 중 하나예요.
- 십진수 리터럴: 십진수 숫자로 시작하고, 십진수 숫자와 밑줄의 어떤 혼합으로 이어져요.
- 16진수 리터럴:
U+0030U+0078(0x) 시퀀스로 시작하고, 16진수 숫자와 밑줄의 어떤 혼합(최소 하나의 숫자)으로 이어져요. - 8진수 리터럴:
U+0030U+006F(0o) 시퀀스로 시작하고, 8진수 숫자와 밑줄의 어떤 혼합(최소 하나의 숫자)으로 이어져요. - 2진수 리터럴:
U+0030U+0062(0b) 시퀀스로 시작하고, 2진수 숫자와 밑줄의 어떤 혼합(최소 하나의 숫자)으로 이어져요.
어떤 리터럴이든 그렇듯 정수 리터럴도 (공백 없이 바로) 접미사가 뒤따를 수 있어요. 접미사는 e나 E로 시작할 수 없는데, 그러면 부동소수점 리터럴의 지수로 해석되기 때문이에요.
리터럴 표현식으로 받아들여지는 정수 리터럴 예시예요.
#![allow(unused)]
fn main() {
#![allow(overflowing_literals)]
123;
123i32;
123u32;
123_u32;
0xff;
0xff_u8;
0x01_f32; // integer 7986, not floating-point 1.0
0x01_e3; // integer 483, not floating-point 1000.0
0o70;
0o70_i16;
0b1111_1111_1001_0000;
0b1111_1111_1001_0000i64;
0b________1;
0usize;
// These are too big for their type, but are accepted as literal expressions.
128_i8;
256_u8;
// This is an integer literal, accepted as a floating-point literal expression.
5f32;
}
예를 들어 -1i8 은 두 토큰( - 그다음 1i8 )으로 분석된다는 점을 기억해 두세요.
리터럴 표현식으로 받아들여지지 않는 정수 리터럴 예시예요.
#![allow(unused)]
fn main() {
#[cfg(false)] {
0invalidSuffix;
123AFB43;
0b010a;
0xAB_CD_EF_GH;
0b1111_f32;
}
}
잘못된 정수 리터럴 (Invalid integer literals)
어떤 정수 리터럴 형태는 잘못됐어요. 모호성을 피하기 위해 토크나이저는 그것들을 여러 토큰으로 쪼개는 대신 거부해요.
#![allow(unused)]
fn main() {
0b0102; // This is not `0b010` followed by `2`.
0o1279; // This is not `0o127` followed by `9`.
0x80.0; // This is not `0x80` followed by `.` and `0`.
0b101e; // This is not a suffixed literal or `0b101` followed by `e`.
0b; // This is not an integer literal or `0` followed by `b`.
0b_; // This is not an integer literal or `0` followed by `b_`.
2em; // This is not a suffixed literal or `2` followed by `em`.
2.0em; // This is not a suffixed literal or `2.0` followed by `em`.
}
다음 상황들은 모두 오류예요.
- 접미사 없는 2진수·8진수 리터럴 바로 뒤에 (공백 없이) 그 진법 범위 밖의 십진수 숫자가 오는 경우.
- 접미사 없는 2진수·8진수·16진수 리터럴 바로 뒤에 (공백 없이) 마침표 문자가 오는 경우(마침표 뒤에 올 수 있는 것에 대한 제한은 부동소수점 리터럴과 동일).
- 접미사 없는 2진수·8진수 리터럴 바로 뒤에 (공백 없이)
e나E가 오는 경우. - 진법 접두어 뒤에, 선택적인 선행 밑줄 이후, 그 진법에 유효한 숫자가 최소한 하나 오지 않는 경우.
튜플 인덱스 (Tuple index)
TUPLE_INDEX → DEC_LITERAL | BIN_LITERAL | OCT_LITERAL | HEX_LITERAL
튜플 인덱스는 튜플, 튜플 구조체, 튜플 enum 변형의 필드를 가리키는 데 쓰여요.
튜플 인덱스는 리터럴 토큰과 직접 비교돼요. 튜플 인덱스는 0에서 시작하고, 각 연속 인덱스는 십진수 값으로 1씩 증가해요. 그래서 십진수 값만 매치되고, 값에 불필요한 0 접두어가 있으면 안 돼요. 튜플 인덱스에는 접미사(예: usize)를 붙일 수 없어요.
#![allow(unused)]
fn main() {
let example = ("dog", "cat", "horse");
let dog = example.0;
let cat = example.1;
// The following examples are invalid.
let cat = example.01; // ERROR no field named `01`
let horse = example.0b10; // ERROR no field named `0b10`
let unicorn = example.0usize; // ERROR suffixes on a tuple index are invalid
let underscore = example.0_0; // ERROR no field `0_0` on type `(&str, &str, &str)`
}
부동소수점 리터럴 (Floating-point literals)
FLOAT_LITERAL → DEC_LITERAL ( . DEC_LITERAL )? FLOAT_EXPONENT SUFFIX?
| DEC_LITERAL . DEC_LITERAL SUFFIX?
| DEC_LITERAL . !( . | _ | XID_Start )
FLOAT_EXPONENT → ( e | E ) ^ ( + | - )? _* DEC_DIGIT ( DEC_DIGIT | _ )*
부동소수점 리터럴은 두 가지 형태 중 하나예요.
- 십진수 리터럴 뒤에 마침표 문자
U+002E(.)가 오는 것. 선택적으로 또 다른 십진수 리터럴과 선택적인 지수가 뒤따를 수 있어요. - 단일 십진수 리터럴 뒤에 지수가 오는 것.
정수 리터럴처럼 부동소수점 리터럴도 접미사가 뒤따를 수 있어요. 단, 접미사 앞부분이 U+002E(.)로 끝나면 안 돼요. 리터럴에 지수가 포함되어 있지 않다면 접미사는 e나 E로 시작할 수 없어요.
리터럴 표현식으로 받아들여지는 부동소수점 리터럴 예시예요.
#![allow(unused)]
fn main() {
123.0f64;
0.1f64;
0.1f32;
12E+99_f64;
let x: f64 = 2.;
}
마지막 예시는 마침표로 끝나는 부동소수점 리터럴에는 접미사 문법을 쓸 수 없기 때문에 특별해요. 2.f64 는 2에 f64라는 이름의 메서드를 호출하려는 시도로 해석될 거예요.
예를 들어 -1.0 은 두 토큰(- 그다음 1.0 )으로 분석된다는 점을 기억해 두세요.
리터럴 표현식으로 받아들여지지 않는 부동소수점 리터럴 예시예요.
#![allow(unused)]
fn main() {
#[cfg(false)] {
2.0f80;
2e5f80;
2e5e6;
2.0e5e6;
1.3e10u64;
}
}
숫자 없는 지수: 지수에 숫자가 없는 부동소수점 리터럴은 오류예요.
#![allow(unused)]
fn main() {
2e; // This is not a floating-point literal or `2` followed by `e`.
2.0e; // This is not a floating-point literal or `2.0` followed by `e`.
}
수명과 루프 레이블 (Lifetimes and loop labels)
LIFETIME_TOKEN → RAW_LIFETIME
| ' IDENTIFIER_OR_KEYWORD !'
LIFETIME_OR_LABEL → RAW_LIFETIME
| ' NON_KEYWORD_IDENTIFIER !'
RAW_LIFETIME → 'r# ^ IDENTIFIER_OR_KEYWORD !'
RESERVED_RAW_LIFETIME → 'r# ( _ | crate | self | Self | super ) !( ' | XID_Continue )
수명 매개변수와 루프 레이블은 LIFETIME_OR_LABEL 토큰을 사용해요. 어떤 LIFETIME_TOKEN이든 렉서가 받아들이고, 예를 들어 매크로에서 쓸 수 있어요.
raw 수명(raw lifetime) 은 보통 수명과 같지만 식별자 앞에 r# 가 붙어요. (r# 접두어는 실제 수명의 일부로 포함되지 않아요.) 보통 수명과 달리 raw 수명은 위 RAW_LIFETIME 에 나열된 것들을 제외한 어떤 엄격(strict) 또는 예약(reserved) 키워드든 될 수 있어요. RESERVED_RAW_LIFETIME 토큰을 쓰는 것은 오류예요.
2021 에디션 차이: Raw 수명은 2021 에디션 이상에서 받아들여져요. 이전 에디션에서는
'r#lt토큰이'r # lt로 렉싱돼요.
구두점 (Punctuation)
구두점 토큰은 연산자, 구분자, 그리고 문법의 다른 부분으로 쓰여요.
PUNCTUATION → ...
| ..=
| <<=
| >>=
| !=
| %=
| &&
| &=
| *=
| +=
| -=
| ->
| ..
| /=
| ::
| <-
| <<
| <=
| ==
| =>
| >=
| >>
| ^=
| |=
| ||
| !
| #
| $
| %
| &
| (
| )
| *
| +
| ,
| -
| .
| /
| :
| ;
| <
| =
| >
| ?
| @
| [
| ]
| ^
| {
| |
| }
| ~
참고: 구두점 문자가 어떻게 쓰이는지에 대한 링크는 syntax index를 참고해요.
구분자 (Delimiters)
대괄호 구두점은 문법의 여러 부분에서 쓰여요. 열린 괄호는 항상 닫힌 괄호와 짝을 이뤄야 해요. 괄호와 그 안의 토큰들을 매크로에서 "토큰 트리(token tree)" 라고 불러요. 세 가지 종류의 괄호는 이래요.
| 괄호 | 종류 |
|---|---|
{ } |
중괄호 (Curly braces) |
[ ] |
대괄호 (Square brackets) |
( ) |
소괄호 (Parentheses) |
예약 토큰 (Reserved tokens)
몇몇 토큰 형태는 미래 사용을 위해 또는 혼동을 피하기 위해 예약되어 있어요. 소스 입력이 이 형태들 중 하나와 일치하면 오류예요.
RESERVED_TOKEN → RESERVED_GUARDED_STRING_LITERAL
| RESERVED_POUNDS
| RESERVED_RAW_IDENTIFIER
| RESERVED_RAW_LIFETIME
| RESERVED_TOKEN_DOUBLE_QUOTE
| RESERVED_TOKEN_LIFETIME
| RESERVED_TOKEN_POUND
| RESERVED_TOKEN_SINGLE_QUOTE
예약 접두어 (Reserved prefixes)
RESERVED_TOKEN_DOUBLE_QUOTE → IDENTIFIER_OR_KEYWORD except `b` or `c` or `r` or `br` or `cr` "
RESERVED_TOKEN_SINGLE_QUOTE → IDENTIFIER_OR_KEYWORD except `b` '
RESERVED_TOKEN_POUND → IDENTIFIER_OR_KEYWORD except `r` or `br` or `cr` #
RESERVED_TOKEN_LIFETIME → ' IDENTIFIER_OR_KEYWORD except `r` #
예약 접두어(reserved prefix) 로 알려진 몇몇 어휘 형태는 미래 사용을 위해 예약되어 있어요. 달리 렉시컬하게 non-raw 식별자(또는 키워드)로 해석될 소스 입력이 (공백 없이) 바로 #, ', " 문자 뒤에 오면 예약 접두어로 식별돼요.
raw 식별자, raw 문자열 리터럴, raw 바이트 문자열 리터럴은 # 문자를 담을 수 있지만 예약 접두어가 포함된 것으로 해석되지 않아요. 마찬가지로 raw 문자열 리터럴, 바이트 리터럴, 바이트 문자열 리터럴, raw 바이트 문자열 리터럴, C 문자열 리터럴, raw C 문자열 리터럴에 쓰이는 r, b, br, c, cr 접두어도 예약 접두어로 해석되지 않아요.
달리 렉시컬하게 non-raw 수명(또는 키워드)으로 해석될 소스 입력이 (공백 없이) 바로 # 문자 뒤에 오면 예약 수명 접두어로 식별돼요.
2021 에디션 차이: 2021 에디션부터 예약 접두어는 렉서가 오류로 보고해요(특히 매크로에 전달할 수 없어요). 2021 에디션 이전에는 렉서가 받아들이고 여러 토큰(예: 식별자/키워드 토큰 하나 다음에
#토큰)으로 해석했어요.
모든 에디션에서 받아들여지는 예시예요.
#![allow(unused)]
fn main() {
macro_rules! lexes {($($_:tt)*) => {}}
lexes!{a #foo}
lexes!{continue 'foo}
lexes!{match "..." {}}
lexes!{r#let#foo} // three tokens: r#let # foo
lexes!{'prefix #lt}
}
2021 에디션 이전에 받아들여졌지만 이후 거부되는 예시예요.
#![allow(unused)]
fn main() {
macro_rules! lexes {($($_:tt)*) => {}}
lexes!{a#foo}
lexes!{continue'foo}
lexes!{match"..." {}}
lexes!{'prefix#lt}
}
예약 가드 (Reserved guards)
RESERVED_GUARDED_STRING_LITERAL → #+ STRING_LITERAL
RESERVED_POUNDS → #2..
예약 가드(reserved guard)는 미래 사용을 위해 예약된 문법으로, 쓰면 컴파일 오류를 만들어요.
- 예약 가드 문자열 리터럴은 하나 이상의
U+0023(#) 바로 뒤에STRING_LITERAL이 오는 토큰이에요. - 예약 파운드(reserved pounds) 는 둘 이상의
U+0023(#) 토큰이에요.
2024 에디션 차이: 2024 에디션 이전에는 예약 가드가 렉서가 받아들이고 여러 토큰으로 해석했어요. 예를 들어
#"foo"#형태는 세 토큰으로,##은 두 토큰으로 해석됐어요.
각주
[1] raw 리터럴에서 각 면의 # 개수는 동일해야 해요.
[2] 모든 숫자 리터럴은 _ 를 시각적 구분자로 허용해요: 1_234.0E+18f64
[3] \u{...} 형태에 대한 자세한 내용은 문자 이스케이프를 참고해요.
더 알아보기 (Learn more)
- 키워드 (Keywords) — 예약된 단어들
- 식별자 (Identifiers) — 이름을 짓는 규칙
- 리터럴 표현식 — 리터럴이 표현식으로 쓰일 때