Rust 문자열 개념 탐구

Beginner

This tutorial is from open-source community. Access the source code

소개

이 랩에서는 Rust 의 문자열 개념을 탐구합니다. Rust 에는 두 가지 유형의 문자열이 있습니다: String&str.

String은 힙에 할당되고, 크기가 늘어날 수 있으며, 유효한 UTF-8 시퀀스임을 보장하는 문자열입니다. 반면에, &str은 유효한 UTF-8 시퀀스를 가리키는 슬라이스이며, String을 보기 위해 사용될 수 있습니다.

Rust 에서 문자열 리터럴은 특수 문자를 표현하기 위해 이스케이프를 사용하는 것을 포함하여 다양한 방식으로 작성할 수 있습니다. 예를 들어, \x3F는 물음표 문자를 나타내고, \u{211D}는 유니코드 코드 포인트를 나타냅니다. 이스케이프 없이 문자열을 있는 그대로 작성하려면 raw 문자열 리터럴을 사용할 수도 있습니다.

바이트 문자열로 작업해야 하는 경우, Rust 는 b 접두사를 사용하여 바이트 문자열 리터럴을 제공합니다. 바이트 문자열은 바이트 이스케이프를 가질 수 있지만, 유니코드 이스케이프는 가질 수 없습니다. Raw 바이트 문자열도 raw 문자열 리터럴과 유사한 방식으로 사용할 수 있습니다.

strString은 항상 유효한 UTF-8 시퀀스여야 한다는 점에 유의하는 것이 중요합니다. 다른 인코딩으로 문자열을 사용해야 하는 경우, 문자 인코딩 간의 변환을 위해 encoding과 같은 외부 크레이트 (crate) 를 사용할 수 있습니다.

참고: 랩에서 파일 이름을 지정하지 않은 경우, 원하는 파일 이름을 사용할 수 있습니다. 예를 들어, main.rs를 사용하고, rustc main.rs && ./main으로 컴파일하고 실행할 수 있습니다.

문자열 (Strings)

Rust 에는 두 가지 유형의 문자열이 있습니다: String&str.

String은 바이트 벡터 (Vec<u8>) 로 저장되지만, 항상 유효한 UTF-8 시퀀스임을 보장합니다. String은 힙에 할당되고, 크기가 늘어날 수 있으며, null 로 종료되지 않습니다.

&str은 슬라이스 (&[u8]) 로, 항상 유효한 UTF-8 시퀀스를 가리키며, &[T]Vec<T>를 보는 것처럼 String을 보기 위해 사용될 수 있습니다.

fn main() {
    // (모든 타입 어노테이션은 불필요합니다)
    // 읽기 전용 메모리에 할당된 문자열에 대한 참조
    let pangram: &'static str = "the quick brown fox jumps over the lazy dog";
    println!("Pangram: {}", pangram);

    // 단어를 역순으로 반복합니다. 새로운 문자열은 할당되지 않습니다.
    println!("Words in reverse");
    for word in pangram.split_whitespace().rev() {
        println!("> {}", word);
    }

    // 문자를 벡터로 복사하고, 정렬하고, 중복을 제거합니다.
    let mut chars: Vec<char> = pangram.chars().collect();
    chars.sort();
    chars.dedup();

    // 비어 있고 크기가 늘어날 수 있는 `String` 을 생성합니다.
    let mut string = String::new();
    for c in chars {
        // 문자열 끝에 문자를 삽입합니다.
        string.push(c);
        // 문자열 끝에 문자열을 삽입합니다.
        string.push_str(", ");
    }

    // 트리밍된 문자열은 원래 문자열의 슬라이스이므로, 새로운 할당이 수행되지 않습니다.
    let chars_to_trim: &[char] = &[' ', ','];
    let trimmed_str: &str = string.trim_matches(chars_to_trim);
    println!("Used characters: {}", trimmed_str);

    // 힙에 문자열을 할당합니다.
    let alice = String::from("I like dogs");
    // 새로운 메모리를 할당하고 수정된 문자열을 저장합니다.
    let bob: String = alice.replace("dog", "cat");

    println!("Alice says: {}", alice);
    println!("Bob says: {}", bob);
}

더 많은 str/String 메서드는 std::strstd::string 모듈에서 찾을 수 있습니다.

리터럴과 이스케이프 (Literals and escapes)

특수 문자가 포함된 문자열 리터럴을 작성하는 방법에는 여러 가지가 있습니다. 모두 유사한 &str을 생성하므로, 작성하기에 가장 편리한 형식을 사용하는 것이 좋습니다. 마찬가지로, 바이트 문자열 리터럴을 작성하는 방법도 여러 가지가 있으며, 모두 &[u8; N]을 생성합니다.

일반적으로 특수 문자는 백슬래시 문자 (\) 로 이스케이프됩니다. 이렇게 하면 인쇄할 수 없는 문자나 입력 방법을 모르는 문자까지 포함하여 모든 문자를 문자열에 추가할 수 있습니다. 리터럴 백슬래시를 원하면 다른 백슬래시로 이스케이프합니다: \\

리터럴 내에서 발생하는 문자열 또는 문자 리터럴 구분 기호는 이스케이프해야 합니다: "\"", '\''.

fn main() {
    // 16 진수 값을 사용하여 바이트를 작성하기 위해 이스케이프를 사용할 수 있습니다...
    let byte_escape = "I'm writing \x52\x75\x73\x74!";
    println!("What are you doing\x3F (\\x3F means ?) {}", byte_escape);

    // ...또는 유니코드 코드 포인트를 사용할 수 있습니다.
    let unicode_codepoint = "\u{211D}";
    let character_name = "\"DOUBLE-STRUCK CAPITAL R\"";

    println!("Unicode character {} (U+211D) is called {}",
                unicode_codepoint, character_name );


    let long_string = "String literals
                        can span multiple lines.
                        The linebreak and indentation here ->\
                        <- can be escaped too!";
    println!("{}", long_string);
}

때로는 이스케이프해야 하는 문자가 너무 많거나, 문자열을 있는 그대로 작성하는 것이 훨씬 더 편리할 수 있습니다. 이럴 때 raw 문자열 리터럴이 사용됩니다.

fn main() {
    let raw_str = r"Escapes don't work here: \x3F \u{211D}";
    println!("{}", raw_str);

    // raw 문자열에 따옴표가 필요한 경우, #을 두 쌍 추가합니다.
    let quotes = r#"And then I said: "There is no escape!""#;
    println!("{}", quotes);

    // 문자열에 "#"이 필요한 경우, 구분 기호에 더 많은 #을 사용하면 됩니다.
    // 최대 65535 개의 #을 사용할 수 있습니다.
    let longer_delimiter = r###"A string with "## in it. And even "##!"###;
    println!("{}", longer_delimiter);
}

UTF-8 이 아닌 문자열을 원하십니까? (기억하세요, strString은 유효한 UTF-8 이어야 합니다). 아니면 대부분 텍스트인 바이트 배열을 원하십니까? 바이트 문자열이 해결해 드립니다!

use std::str;

fn main() {
    // 이것은 실제로 `&str` 이 아닙니다.
    let bytestring: &[u8; 21] = b"this is a byte string";

    // 바이트 배열에는 `Display` 트레이트가 없으므로, 인쇄하는 데 약간 제한이 있습니다.
    println!("A byte string: {:?}", bytestring);

    // 바이트 문자열은 바이트 이스케이프를 가질 수 있습니다...
    let escaped = b"\x52\x75\x73\x74 as bytes";
    // ...하지만 유니코드 이스케이프는 없습니다.
    // let escaped = b"\u{211D} is not allowed";
    println!("Some escaped bytes: {:?}", escaped);


    // Raw 바이트 문자열은 raw 문자열과 동일하게 작동합니다.
    let raw_bytestring = br"\u{211D} is not escaped here";
    println!("{:?}", raw_bytestring);

    // 바이트 배열을 `str` 로 변환하는 데 실패할 수 있습니다.
    if let Ok(my_str) = str::from_utf8(raw_bytestring) {
        println!("And the same as text: '{}'", my_str);
    }

    let _quotes = br#"You can also use "fancier" formatting, \
                    like with normal raw strings"#;

    // 바이트 문자열은 UTF-8 일 필요가 없습니다.
    let shift_jis = b"\x82\xe6\x82\xa8\x82\xb1\x82\xbb"; // "ようこそ" in SHIFT-JIS

    // 하지만 항상 `str` 로 변환할 수 있는 것은 아닙니다.
    match str::from_utf8(shift_jis) {
        Ok(my_str) => println!("Conversion successful: '{}'", my_str),
        Err(e) => println!("Conversion failed: {:?}", e),
    };
}

문자 인코딩 간의 변환을 위해서는 encoding 크레이트를 확인하십시오.

문자열 리터럴을 작성하고 문자를 이스케이프하는 방법에 대한 자세한 목록은 Rust Reference 의 'Tokens' 챕터에서 제공됩니다.

요약

축하합니다! 문자열 랩을 완료했습니다. LabEx 에서 더 많은 랩을 연습하여 실력을 향상시킬 수 있습니다.