러스트 코어는 스트링 슬라이스인 str
만 제공한다.
String
타입은 표준 라이브러리를 통해 제공된다.
- 가변적
- 소유권
- UTF-8 인코딩(어떤 문자라도 포함할 수 있다.)
new
함수로 비어있는 스트링을 생성할 수 있다.
let mut s = String::new();
to_string
메서드로 초기값이 있는 스트링을 만든다.
Display
트레잇이 구현된 타입은 모두 to_string
메서드 사용이 가능하다.
let s = "initial contents".to_string();
String::from
함수로도 스트링 리터럴에서 String
을 생성할 수 있다.
let s = String::from("initial contents");
String::from
과 .to_string
은 기능이 똑같아서 어떤 것을 사용할 지는 개발자 마음이다.
push_str
메서드로 String
을 늘릴 수 있다.
let mut s = String::from("foo");
s.push_str("bar");
push_str
메서드는 파라미터의 소유권을 가져올 필요가 없어서 스트링 슬라이스를 파라미터로 갖는다.
아래 코드에서 s2
는 소유권이 보존돼서 오류가 나지 않는다.
let mut s1 = String::from("foo");
let s2 = "bar";
s1.push_str(&s2);
println!("s2 is {}", s2);
push
로 문자 하나를 String
에 추가할 수 있다.
let mut s = String::from("lo");
s.push('l');
+
연산자로 두 개의 스트링을 조합할 수 있다.
let s1 = String::from("Hello, ");
let s2 = String::from("world!");
let s3 = s1 + &s2; // s1은 이동되어 유효하지 않아짐
+
연산은 아래 처럼 생긴 add
메서드를 사용하는데 String
에 &str
을 더하는 형태이다.
fn add(self, s: &str) -> String {
&s2
는 &String
이지만 역참조 강제에 의해 &str
로 강제 변환된다.
&s2
가 &s2[..]
로 바뀌는 것이다.
add
가 s2
의 소유권은 가져가지 않지만
&
가 없는 self
로 s1
을 인자로 받아서 s1
의 소유권을 가져간다.
format!
매크로는 println!
처럼 작동하면서 결과를 스크린에 출력하는 대신 String
을 반환한다.
또한, format!
은 파라미터의 소유권을 가져가지 않는다.
let s1 = String::from("tic");
let s2 = String::from("tac");
let s3 = String::from("toe");
let s = format!("{}-{}-{}", s1, s2, s3);
는 지원되지 않는다.
String
은 Vec<u8>
을 감싼 것이다.
아래의 len
은 4인 반면
let len = String::from("Hola").len();
아래의 len
은 12가 아닌 24이다.
let len = String::from("Здравствуйте").len();
각각의 유니코드 스칼라 값이 2바이트 씩 차지하기 때문이다.
“नमस्ते” 이렇게 생긴 힌디어는 18바이트의 Vec<u8>
로 저장된다.
[224, 164, 168, 224, 164, 174, 224, 164, 184, 224, 165, 141, 224, 164, 164, 224, 165, 135]
char
타입으로 본다면 이렇다.
['न', 'म', 'स', '्', 'त', 'े']
우리가 보는 글자처럼 문자로 클러스터로 보면 이렇다.
["न", "म", "स्", "ते"]
셋 중에 어떤게 유효한지는 문자열 내용을 모두 알아야 알 수 있다.
그래서 String
의 인덱스 연산이 O(1) 성능이 보장되지 않는다.
스트링 슬라이스를 만들 때는 스트링 인덱스로 접근하는 것과 같은 연산을 하면 안 된다.
아래는 괜찮은데
let hello = "Здравствуйте";
let s = &hello[0..4];
아래와 같이 하면 인덱스로 접근하는 것과 같아서 런타임에 패닉이 발생한다.
&hello[0..1]
for c in "नमस्ते".chars() {
println!("{}", c);
}
for b in "नमस्ते".bytes() {
println!("{}", b);
}
방법이 복잡해서 라이브러리를 가져다 쓴다.
러스트가 다루기 까다로운데 본성이 복잡한 스트링은 러스트 내에서 다루기 더 까다롭다.
하지만, 까다로움 덕분에 개발 후반에 스트링 관련 에러를 마주할 일을 막을 수 있다.