리터럴 표현식

리터럴 표현식

리터럴 표현식은 토큰의 시퀀스가 아니라 하나의 토큰으로 이루어진 표현식이에요. 즉 어떤 값에 이름을 붙이거나 다른 평가 규칙으로 참조하는 대신, 그 자체가 곧 평가 결과 값인 표기를 바로 나타내요. "hello"5처럼 소스에 직접 적은 값을 말하죠.

리터럴은 상수 표현식의 한 형태라서 (주로) 컴파일 시점에 평가돼요. 앞서 다룬 어휘 리터럴 형태 각각이 리터럴 표현식이 될 수 있고, 키워드 truefalse도 마찬가지예요.

#![allow(unused)]
fn main() {
"hello";   // string type
'5';       // character type
5;         // integer type
}

문법은 다음과 같아요.

LiteralExpression → CHAR_LITERAL
                  | STRING_LITERAL | RAW_STRING_LITERAL
                  | BYTE_LITERAL | BYTE_STRING_LITERAL | RAW_BYTE_STRING_LITERAL
                  | C_STRING_LITERAL | RAW_C_STRING_LITERAL
                  | INTEGER_LITERAL | FLOAT_LITERAL
                  | true | false

출처: Rust Reference

이스케이프

아래에서 설명하는 텍스트 리터럴 표현식은 여러 형태의 이스케이프(escape)를 사용해요. 각 이스케이프는 두 가지로 특징지어져요.

  • 이스케이프 시퀀스(escape sequence) — 항상 U+005C(\)로 시작하는 문자들의 시퀀스
  • 이스케이프된 값(escaped value) — 단일 문자 또는 빈 문자 시퀀스

이스케이프 정의에서 8진수 숫자는 범위 [0-7]의 문자, 16진수 숫자는 범위 [0-9], [a-f], [A-F]의 문자를 말해요.

단순 이스케이프(Simple escapes)

아래 표의 첫 열에 나오는 각 문자 시퀀스는 이스케이프 시퀀스예요. 각 경우 이스케이프된 값은 두 번째 열에 나오는 문자예요.

이스케이프 시퀀스 이스케이프된 값
\0 U+0000 (NUL)
\t U+0009 (HT)
\n U+000A (LF)
\r U+000D (CR)
\" U+0022 (QUOTATION MARK)
\' U+0027 (APOSTROPHE)
\\ U+005C (REVERSE SOLIDUS)

8비트 이스케이프

이스케이프 시퀀스는 \x 뒤에 16진수 숫자 두 개로 구성돼요. 이스케이프된 값은 이스케이프 시퀀스의 마지막 두 문자를 16진수 정수로 해석한 결과(u8::from_str_radix를 radix 16으로 쓴 것처럼)에 해당하는 유니코드 스칼라 값의 문자예요. 따라서 이스케이프된 값의 유니코드 스칼라 값은 u8 범위 안에 있어요.

7비트 이스케이프

이스케이프 시퀀스는 \x 뒤에 8진수 숫자 하나, 그다음 16진수 숫자 하나로 구성돼요. 이스케이프된 값은 마지막 두 문자를 16진수 정수로 해석한 결과에 해당하는 유니코드 스칼라 값의 문자예요.

유니코드 이스케이프

이스케이프 시퀀스는 \u{ 로 시작해서, 각각 16진수 숫자 또는 _ 인 문자들의 시퀀스, 그리고 } 로 구성돼요. 이스케이프된 값은 시퀀스에 든 16진수 숫자를 16진수 정수로 해석한 결과(u32::from_str_radix로 해석한 것처럼)에 해당하는 유니코드 스칼라 값의 문자예요. CHAR_LITERAL 또는 STRING_LITERAL 토큰의 허용 형태 덕분에 항상 그런 문자가 존재해요.

문자열 이어쓰기 이스케이프(String continuation escapes)

이스케이프 시퀀스는 \ 뒤에 바로 U+000A(LF)가 오고, 그다음 다음 비공백 문자 앞의 모든 공백 문자로 구성돼요. 여기서 공백 문자는 U+0009(HT), U+000A(LF), U+000D(CR), U+0020(SPACE)이에요. 이스케이프된 값은 빈 문자 시퀀스예요. 즉 이 형태의 이스케이프는 줄바꿈을 포함한 뒤따르는 공백을 통째로 건너뛰게 해요. 그래서 아래의 a, b, c는 모두 같아요.

#![allow(unused)]
fn main() {
let a = "foobar";
let b = "foo\
         bar";
let c = "foo\

     bar";

assert_eq!(a, b);
assert_eq!(b, c);
}

추가 줄바꿈을 건너뛰는 동작(c 예시)은 혼란스럽고 예상 밖일 수 있어요. 이 동작은 나중에 조정될 수 있어요. 결정이 내려질 때까지는 줄 이어쓰기로 여러 줄바꿈을 건너뛰는 것에 의존하지 않는 걸 권장해요.

문자 리터럴 표현식

문자 리터럴 표현식은 단일 CHAR_LITERAL 토큰으로 이루어져요. 표현식의 타입은 기본형 char예요. 토큰은 접미사를 가질 수 없어요.

토큰의 리터럴 내용(literal content)은 문자열 표현에서 첫 번째 U+0027(') 이후부터 마지막 U+0027(') 이전까지의 문자 시퀀스예요. 리터럴 표현식이 나타내는 문자는 리터럴 내용에서 다음과 같이 유도돼요.

  • 리터럴 내용이 다음 이스케이프 시퀀스 형태 중 하나면, 나타내는 문자는 그 이스케이프의 이스케이프된 값: 단순 이스케이프, 7비트 이스케이프, 유니코드 이스케이프
  • 그 외에는 리터럴 내용을 이루는 단일 문자

표현식의 값은 나타내는 문자의 유니코드 스칼라 값에 해당하는 char예요. CHAR_LITERAL 토큰의 허용 형태 덕분에 이 규칙은 항상 단일 문자를 만들어내요.

#![allow(unused)]
fn main() {
'R';                               // R
'\'';                              // '
'\x52';                            // R
'\u{00E6}';                        // LATIN SMALL LETTER AE (U+00E6)
}

문자열 리터럴 표현식

문자열 리터럴 표현식은 단일 STRING_LITERAL 또는 RAW_STRING_LITERAL 토큰으로 이루어져요. 표현식의 타입은 기본형 str에 대한 (정적 수명의) 공유 참조예요. 즉 타입은 &'static str이에요. 토큰은 접미사를 가질 수 없어요.

토큰의 리터럴 내용은 첫 번째 U+0022(") 이후부터 마지막 U+0022(") 이전까지의 문자 시퀀스예요. 리터럴 표현식이 나타내는 문자열은 리터럴 내용에서 다음과 같이 유도돼요.

  • 토큰이 STRING_LITERAL이면, 리터럴 내용에 나타나는 다음 형태의 이스케이프 시퀀스 각각은 그 이스케이프의 이스케이프된 값으로 대체돼요: 단순 이스케이프, 7비트 이스케이프, 유니코드 이스케이프, 문자열 이어쓰기 이스케이프. 이 대체는 왼쪽에서 오른쪽 순서로 일어나요. 예를 들어 토큰 "\\x41"은 문자 \, x, 4, 1로 변환돼요.
  • 토큰이 RAW_STRING_LITERAL이면, 나타내는 문자열은 리터럴 내용 그 자체예요.

표현식의 값은 나타내는 문자열의 UTF-8 인코딩을 담은 정적으로 할당된 str에 대한 참조예요.

#![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_LITERAL 토큰으로 이루어져요. 표현식의 타입은 기본형 u8이에요. 토큰은 접미사를 가질 수 없어요.

리터럴 내용은 첫 번째 U+0027(') 이후부터 마지막 U+0027(') 이전까지의 문자 시퀀스예요. 나타내는 문자는 리터럴 내용에서 다음과 같이 유도돼요.

  • 리터럴 내용이 다음 이스케이프 시퀀스 형태 중 하나면, 나타내는 문자는 그 이스케이프의 이스케이프된 값: 단순 이스케이프, 8비트 이스케이프
  • 그 외에는 리터럴 내용을 이루는 단일 문자

표현식의 값은 나타내는 문자의 유니코드 스칼라 값이에요. BYTE_LITERAL 토큰의 허용 형태 덕분에 이 규칙은 항상 단일 문자를 만들어내고, 그 유니코드 스칼라 값은 u8 범위 안이에요.

#![allow(unused)]
fn main() {
b'R';                              // 82
b'\'';                             // 39
b'\x52';                           // 82
b'\xA0';                           // 160
}

바이트 문자열 리터럴 표현식

바이트 문자열 리터럴 표현식은 단일 BYTE_STRING_LITERAL 또는 RAW_BYTE_STRING_LITERAL 토큰으로 이루어져요. 표현식의 타입은 요소 타입이 u8인 배열에 대한 (정적 수명의) 공유 참조예요. 즉 타입은 &'static [u8; N]인데, 여기서 N은 아래에서 설명하는 나타내는 문자열의 바이트 수예요. 토큰은 접미사를 가질 수 없어요.

리터럴 내용은 첫 번째 U+0022(") 이후부터 마지막 U+0022(") 이전까지의 문자 시퀀스예요. 나타내는 문자열은 리터럴 내용에서 다음과 같이 유도돼요.

  • 토큰이 BYTE_STRING_LITERAL이면, 리터럴 내용에 나타나는 다음 형태의 이스케이프 시퀀스 각각은 그 이스케이프의 이스케이프된 값으로 대체돼요: 단순 이스케이프, 8비트 이스케이프, 문자열 이어쓰기 이스케이프. 이 대체는 왼쪽에서 오른쪽 순서로 일어나요. 예를 들어 토큰 b"\\x41"은 문자 \, x, 4, 1로 변환돼요.
  • 토큰이 RAW_BYTE_STRING_LITERAL이면, 나타내는 문자열은 리터럴 내용 그 자체예요.

표현식의 값은 나타내는 문자열의 문자들의 유니코드 스칼라 값을 같은 순서로 담은 정적으로 할당된 배열에 대한 참조예요. BYTE_STRING_LITERALRAW_BYTE_STRING_LITERAL 토큰의 허용 형태 덕분에 이 규칙은 항상 u8 범위 안의 배열 요소 값을 만들어내요.

#![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 문자열 리터럴 표현식은 단일 C_STRING_LITERAL 또는 RAW_C_STRING_LITERAL 토큰으로 이루어져요. 표현식의 타입은 표준 라이브러리의 CStr 타입에 대한 (정적 수명의) 공유 참조예요. 즉 타입은 &'static core::ffi::CStr이에요. 토큰은 접미사를 가질 수 없어요.

리터럴 내용은 첫 번째 " 이후부터 마지막 " 이전까지의 문자 시퀀스예요. 나타내는 바이트는 리터럴 내용에서 다음과 같이 유도돼요.

  • 토큰이 C_STRING_LITERAL이면, 리터럴 내용은 항목들의 시퀀스(각각 \가 아닌 단일 유니코드 문자 또는 이스케이프)로 취급돼요. 그 시퀀스는 다음과 같이 바이트 시퀀스로 변환돼요. 각 단일 유니코드 문자는 자기 UTF-8 표현을 기여하고, 각 단순 이스케이프는 자기 이스케이프된 값의 유니코드 스칼라 값을, 각 8비트 이스케이프는 자기 이스케이프된 값의 유니코드 스칼라 값을 담은 단일 바이트를, 각 유니코드 이스케이프는 자기 이스케이프된 값의 UTF-8 표현을 기여해요. 각 문자열 이어쓰기 이스케이프는 바이트를 기여하지 않아요.
  • 토큰이 RAW_C_STRING_LITERAL이면, 나타내는 바이트는 리터럴 내용의 UTF-8 인코딩이에요.

C_STRING_LITERALRAW_C_STRING_LITERAL 토큰의 허용 형태 덕분에 나타내는 바이트에는 널 바이트가 절대 포함되지 않아요. 표현식의 값은 나타내는 바이트 뒤에 널 바이트가 이어지는 바이트 배열을 담은 정적으로 할당된 CStr에 대한 참조예요.

#![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

c"æ";                                // LATIN SMALL LETTER AE (U+00E6)
c"\u{00E6}";                         // LATIN SMALL LETTER AE (U+00E6)
c"\xC3\xA6";                         // LATIN SMALL LETTER AE (U+00E6)

c"\xE6".to_bytes();                  // [230]
c"\u{00E6}".to_bytes();              // [195, 166]
}

정수 리터럴 표현식

정수 리터럴 표현식은 단일 INTEGER_LITERAL 토큰으로 이루어져요.

토큰에 접미사가 있으면, 그 접미사는 기본 정수 타입(u8, i8, u16, i16, u32, i32, u64, i64, u128, i128, usize, isize) 중 하나의 이름이어야 하고, 표현식은 그 타입을 가져요. 토큰에 접미사가 없으면 표현식의 타입은 타입 추론으로 정해져요.

  • 주변 프로그램 맥락에서 정수 타입을 유일하게 결정할 수 있으면, 표현식은 그 타입을 가져요.
  • 프로그램 맥락이 타입을 덜 제약하면, 부호 있는 32비트 정수 i32로 기본 설정돼요.
  • 프로그램 맥락이 타입을 과도하게 제약하면 정적 타입 오류로 간주돼요.
#![allow(unused)]
fn main() {
123;                               // type i32
123i32;                            // type i32
123u32;                            // type u32
123_u32;                           // type u32
let a: u64 = 123;                  // type u64

0xff;                              // type i32
0xff_u8;                           // type u8

0o70;                              // type i32
0o70_i16;                          // type i16

0b1111_1111_1001_0000;             // type i32
0b1111_1111_1001_0000i64;          // type i64

0usize;                            // type usize
}

표현식의 값은 토큰의 문자열 표현으로부터 다음과 같이 결정돼요.

  • 문자열의 첫 두 문자를 보고 정수 진법(radix)이 선택돼요. 0b는 2진법, 0o는 8진법, 0x는 16진법을 나타내고, 그 외에는 10진법이에요.
  • 진법이 10이 아니면 문자열에서 첫 두 문자가 제거돼요.
  • 접미사가 있으면 문자열에서 제거돼요.
  • 밑줄은 모두 제거돼요.
  • 문자열은 선택된 진법으로 u128::from_str_radix로 해석한 것처럼 u128 값으로 변환돼요. 값이 u128에 맞지 않으면 컴파일 오류예요.
  • u128 값은 숫자 캐스트(numeric cast)를 통해 표현식의 타입으로 변환돼요.

마지막 캐스트는 리터럴 값이 표현식 타입에 맞지 않으면 값을 잘라낼(truncate) 수 있어요. rustc에는 overflowing_literals라는 린트 검사가 있는데, 기본적으로 deny로 설정되어 이 상황을 거부해요.

-1i8 같은 것은 리터럴 표현식 1i8에 부정 연산자를 적용한 거지, 단일 정수 리터럴 표현식이 아니에요. 부호 있는 타입에서 가장 음인 값을 표현하는 방법은 Overflow 문서를 참고하세요.

부동소수점 리터럴 표현식

부동소수점 리터럴 표현식은 두 가지 형태 중 하나예요.

  • 단일 FLOAT_LITERAL 토큰
  • 접미사가 있고 진법 표시가 없는 단일 INTEGER_LITERAL 토큰

토큰에 접미사가 있으면, 그 접미사는 기본 부동소수점 타입(f32 또는 f64) 중 하나의 이름이어야 하고, 표현식은 그 타입을 가져요. 토큰에 접미사가 없으면 표현식의 타입은 타입 추론으로 정해져요.

  • 주변 맥락에서 부동소수점 타입을 유일하게 결정할 수 있으면, 표현식은 그 타입을 가져요.
  • 맥락이 타입을 덜 제약하면 f64로 기본 설정돼요.
  • 맥락이 타입을 과도하게 제약하면 정적 타입 오류로 간주돼요.
#![allow(unused)]
fn main() {
123.0f64;        // type f64
0.1f64;          // type f64
0.1f32;          // type f32
12E+99_f64;      // type f64
5f32;            // type f32
let x: f64 = 2.; // type f64
}

표현식의 값은 토큰의 문자열 표현으로부터 다음과 같이 결정돼요.

  • 접미사가 있으면 문자열에서 제거돼요.
  • 밑줄은 모두 제거돼요.
  • 문자열은 f32::from_str 또는 f64::from_str로 해석한 것처럼 표현식의 타입으로 변환돼요.

-1.0 같은 것은 리터럴 표현식 1.0에 부정 연산자를 적용한 거지, 단일 부동소수점 리터럴 표현식이 아니에요. 그리고 infNaN은 리터럴 토큰이 아니에요. 리터럴 표현식 대신 f32::INFINITY, f64::INFINITY, f32::NAN, f64::NAN 상수를 쓸 수 있어요. rustc에서 무한대로 평가될 만큼 큰 리터럴은 overflowing_literals 린트 검사를 발동시켜요.

불리언 리터럴 표현식

불리언 리터럴 표현식은 키워드 true 또는 false 중 하나로 이루어져요. 표현식의 타입은 기본 불리언 타입이에요. 값은 키워드가 truetrue, falsefalse예요.

더 알아보기