[Rust]
- [Rust] Rust 설치
- [Rust] Hello Cargo
- [Rust] VSCode 설정
- [Rust] Rust 공부 시작
- [Rust] Rust 기초
- [Rust] Rust 제어문
- [Rust] Rust 자료형
- [Rust] Rust 제네릭
- [Rust] Rust 소유권, 대여, 데이터
- [Rust] study Text
- [Rust] Rust 객체지향 프로그래밍
- [Rust] Rust 스마트 포인터
- [Rust] Rust 프로젝트 구성 및 구조
텍스트
이제 Rust가 메모리를 어찌 다루는지 살짝 이해했으니, 텍스트에 대해 더 자세히 알아볼 준비가 된 것 같습니다. Rust는 아마도 여러분이 다른 언어에서는 익숙하지 않았을 만국의 텍스트와 바이트 수준의 관심사를 상당히 신경써서 다룹니다. 즉, Rust는 그러한 관심사를 다룰 다양하고 훌륭한 도구들을 갖고 있습니다.
1. 문자열
문자열은 언제나 유니코드로 되어 있습니다.
문자열의 자료형은 &'static str
입니다:
&
는 메모리 내의 장소를 참조하고 있다는 의미이며,&mut
가 빠졌다는 것은 컴파일러가 값의 변경을 허용하지 않을 것이라는 뜻입니다'static
은 string 데이터가 프로그램이 끝날 때까지 유효하다는 의미입니다 (절대 drop 되지 않습니다)str
은 언제나 유효한 utf-8인 바이트 열을 가리키고 있다는 의미입니다
메모리 상세:
- Rust 컴파일러는 문자열을 프로그램 메모리의 데이터 세그먼트에 저장할 것입니다
1
2
3
4
fn main() {
let a: &'static str = "hi 🦀";
println!("{} {}", a, a.len());
}
$ hi 🦀 7
2. utf-8이란 무엇인가
컴퓨터로 점점 더 많은 나라의 언어가 사용되면서, 세계는 ASCII가 허용하던 것보다 (1 바이트는 256 문자 밖에 허용하지 않음) 더 많은 문자를 필요로 하게 되었습니다.
utf-8은 1에서 4 바이트의 가변 길이 바이트로 도입되어, 사용 가능한 문자의 범위를 엄청나게 늘려 주었습니다.
가변 크기 문자의 한 가지 장점은 매우 흔한 ASCII 문자(utf-8에서도 여전히 1 바이트만 필요로 함)에 쓸데 없는 바이트를 필요로 하지 않는다는 점입니다.
한 가지 단점은 단순한 인덱싱(예: my_text[3]
으로 네 번째 문자를 가져옴)으로는 더 이상 빠르게(O(1) 상수 시간으로) 문자를 찾을 수 없다는 점입니다. 바로 앞의 글자가 가변 길이를 가질 수 있어, 바이트 열에서 4번째 문자가 실제로 시작하는 위치가 달라질 수도 있습니다.
우리는 그 대신에 utf-8 바이트 열을 하나하나 돌면서 각각의 유니코드 문자가 실제로 어디에서 시작하는지 찾아야 합니다 (O(n) 선형 시간).
Ferris: “utf-8로 내 물밑 친구들의 이모지를 보여줄 수 있어서 기쁩니다.”
🐠🐙🐟🐬🐋
3. 예외처리문자
어떤 문자들은 시각적으로 표현하기 어려우므로, 예외처리 코드(escape code) 로 대체하여 씁니다.
Rust는 C 기반 언어들의 일반적인 예외처리 코드를 지원합니다:
- \n - 줄바꿈
- \r - 캐리지리턴
- \t - 탭
- \ - 역슬래시
- \0 - null
- ' - 작은 따옴표
전체 목록은 여기서 보실 수 있습니다.
1
2
3
4
fn main() {
let a: &'static str = "Ferris가 말하길:\t\"안녕하세요\"";
println!("{}", a);
}
$ Ferris가 말하길: "안녕하세요"
4. 여러 줄로 된 문자열
Rust의 문자열은 기본적으로 여러 줄로 되어 있습니다.
줄바꿈 문자를 원하지 않을 경우, 줄 맨 뒤에 \
를 사용하세요.
1
2
3
4
5
6
7
8
9
10
11
12
13
fn main() {
let haiku: &'static str = "
나는 쓰고, 지우고, 다시 쓴다
다시 지우고, 그러고 나면
양귀비 꽃이 핀다.
- 가쓰시카 호쿠사이";
println!("{}", haiku);
println!(
"hello \
world"
) // w 앞의 공백이 무시 되었음을 주의하세요
}
$ 나는 쓰고, 지우고, 다시 쓴다
$ 다시 지우고, 그러고 나면
$ 양귀비 꽃이 핀다.
$ - 가쓰시카 호쿠사이
$ hello world
5. 원시 문자열
원시 문자열은 r#"
로 시작하고 "#
로 끝나며 문자열을 있는 그대로 쓰는데 쓰입니다. 일반 문자열을 여럿으로 착각하게 할만한 문자들(큰 따옴표나 역슬래시처럼)을 넣을 수 있습니다.
1
2
3
4
5
6
7
8
fn main() {
let a: &'static str = r#"
<div class="advice">
원시 문자열은 일부 상황에서 유용합니다.
</div>
"#;
println!("{}", a);
}
$ <div class="advice">
$ 원시 문자열은 일부 상황에서 유용합니다.
$ </div>
6. 파일에서 문자열 가져오기
매우 큰 텍스트가 필요하다면 include_str!
macro를 이용하여 로컬 파일에서 텍스트를 읽어오는 것을 고려해보세요:
let 00_html = include_str!("00_ko.html");
7. 문자열 슬라이스
문자열 slice는 메모리 상의 바이트 열에 대한 참조이며, 언제나 유효한 utf-8이어야 합니다.
str
slice의 문자열 slice (sub-slice)도 역시 유효한 utf-8이어야 합니다.
&str
의 흔히 사용되는 메소드는 다음과 같습니다:
len
은 문자열의 바이트 길이(글자수가 아닌)를 가져옵니다.starts_with
/ends_with
는 기본적인 비교에 쓰입니다.is_empty
는 길이가 0일 경우 true를 리턴합니다.find
는 주어진 텍스트가 처음 등장하는 위치인Option<usize>
값을 리턴합니다.
1
2
3
4
5
6
7
8
9
fn main() {
let a = "hi 🦀";
println!("{}", a.len());
let first_word = &a[0..2];
let second_word = &a[3..7];
// let half_crab = &a[3..5]; 실패
// Rust는 유효하지 않은 유니코드 문자열의 slice를 허용하지 않습니다
println!("{} {}", first_word, second_word);
}
$ 7
$ hi 🦀
8. 문자
유니코드를 다루는 것이 꽤나 어렵다보니, Rust에서는 utf-8 바이트 열을 char
자료형의 vector로 돌려주는 기능을 제공합니다.
char
하나는 4 바이트입니다 (각각의 문자를 효율적으로 찾을 수 있습니다).
1
2
3
4
5
6
7
8
9
fn main() {
// 문자들을 char의 vector로 가져옵니다
let chars = "hi 🦀".chars().collect::<Vec<char>>();
println!("{}", chars.len()); // 4여야 합니다
// char가 4 바이트이므로 u32로 변환할 수 있습니다
println!("{}", chars[3] as u32);
}
$ 4
$ 129408
9. 스트링
String은 utf-8 바이트 열을 heap memory에 소유하는 struct입니다.
String의 메모리는 heap에 있기 때문에, 문자열과는 달리 늘리거나 변경하거나 기타 등등을 할 수 있습니다.
흔히 쓰는 메소드입니다:
push_str
은 string의 맨 뒤에 utf-8 바이트들을 더 붙일 때 사용합니다.replace
는 utf-8 바이트 열을 다른 것으로 교체할 때 사용합니다.to_lowercase
/to_uppercase
는 대소문자를 바꿀 때 사용합니다.trim
은 공백을 제거할 때 사용합니다.
String이 drop 되면, 그 heap memory도 함께 drop 됩니다.
String
은 &str
로 string을 확장하고 자신을 리턴하는 +
연산자를 갖고 있지만, 기대하는 것만큼 효율적이진 않을 수 있습니다.
1
2
3
4
5
6
fn main() {
let mut helloworld = String::from("hello");
helloworld.push_str(" world");
helloworld = helloworld + "!";
println!("{}", helloworld);
}
hello world!
10. 함수의 매개변수로서의 텍스트
문자열과 string은 일반적으로 함수에 문자열 slice 형태로 전달됩니다. 이 방법은 소유권을 넘길 필요가 없어 대부분의 경우에 유연성을 제공합니다.
1
2
3
4
5
6
7
8
9
10
fn say_it_loud(msg: &str) {
println!("{}!!!", msg.to_string().to_uppercase());
}
fn main() {
// say_it_loud는 &'static str을 &str로 대여할 수 있습니다
say_it_loud("hello");
// say_it_loud는 또한 String을 &str로 대여할 수 있습니다
say_it_loud(&String::from("goodbye"));
}
HELLO!!!
GOODBYE!!!
11. 스트링 만들기
concat
과 join
은 string을 만드는 간단하지만 강력한 방법입니다.
1
2
3
4
5
6
fn main() {
let helloworld = ["hello", " ", "world", "!"].concat();
let abc = ["a", "b", "c"].join(",");
println!("{}", helloworld);
println!("{}",abc);
}
$ hello world!
$ a,b,c
12. 스트링 양식 만들기
format!
macro는 값이 어디에 어떻게 놓일지 매개변수화 된 (예: {}
) string을 정의하여 string을 생성합니다.
format!
은 println!
과 동일한 매개변수화 된 string을 사용합니다.
이 함수의 기능은 너무 방대해서 Tour of Rust의 범위를 넘어가니, 이 문서를 확인하시기 바랍니다.
1
2
3
4
5
fn main() {
let a = 42;
let f = format!("삶, 우주, 그리고 모든 것에 대한 해답: {}", a);
println!("{}", f);
}
$ 삶, 우주, 그리고 모든 것에 대한 해답: 42
13. 스트링 변환
많은 자료형이 to_string
을 이용하여 string으로 변환될 수 있습니다.
generic 함수인 parse
로 string이나 문자열을 다른 자료형을 가진 값으로 변환할 수 있습니다. 이 함수는 실패할 수도 있기 때문에 Result
를 리턴합니다.
1
2
3
4
5
6
7
fn main() -> Result<(), std::num::ParseIntError> {
let a = 42;
let a_string = a.to_string();
let b = a_string.parse::<i32>()?;
println!("{} {}", a, b);
Ok(())
}
$ 42 42
마무리
이제 텍스트의 기본을 배웠습니다! 보셨다시피 유니코드는 텍스트를 다루기 까다롭게 하지만, standard library에는 이를 쉽게 관리할 수 있는 기능들이 많이 있습니다.
지금까지는 주로 절차적 패러다임의 관점으로 Rust를 보아 왔지만 (i.e. 함수와 데이터만), 이제 trait과 더불어 Rust의 객체지향적 패러다임에 의해 잠금 해제될 기능들에 대해 얘기해 볼 시간입니다