토큰

토큰 (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+0030 U+0078(0x) 시퀀스로 시작하고, 16진수 숫자와 밑줄의 어떤 혼합(최소 하나의 숫자)으로 이어져요.
  • 8진수 리터럴: U+0030 U+006F(0o) 시퀀스로 시작하고, 8진수 숫자와 밑줄의 어떤 혼합(최소 하나의 숫자)으로 이어져요.
  • 2진수 리터럴: U+0030 U+0062(0b) 시퀀스로 시작하고, 2진수 숫자와 밑줄의 어떤 혼합(최소 하나의 숫자)으로 이어져요.

어떤 리터럴이든 그렇듯 정수 리터럴도 (공백 없이 바로) 접미사가 뒤따를 수 있어요. 접미사는 eE로 시작할 수 없는데, 그러면 부동소수점 리터럴의 지수로 해석되기 때문이에요.

리터럴 표현식으로 받아들여지는 정수 리터럴 예시예요.

#![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진수 리터럴 바로 뒤에 (공백 없이) eE가 오는 경우.
  • 진법 접두어 뒤에, 선택적인 선행 밑줄 이후, 그 진법에 유효한 숫자가 최소한 하나 오지 않는 경우.

튜플 인덱스 (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(.)로 끝나면 안 돼요. 리터럴에 지수가 포함되어 있지 않다면 접미사는 eE로 시작할 수 없어요.

리터럴 표현식으로 받아들여지는 부동소수점 리터럴 예시예요.

#![allow(unused)]
fn main() {
123.0f64;
0.1f64;
0.1f32;
12E+99_f64;
let x: f64 = 2.;
}

마지막 예시는 마침표로 끝나는 부동소수점 리터럴에는 접미사 문법을 쓸 수 없기 때문에 특별해요. 2.f642f64라는 이름의 메서드를 호출하려는 시도로 해석될 거예요.

예를 들어 -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)