0%

String과 str 차이

러스트의 String과 str의 차이점에 대해 정리합니다.

String

String은 u8타입 벡터(Vector)를 포함한 구조체이다. Rust 오픈소스 페이지에서 아래의 코드를 확인할 수 있다.

1
2
3
pub struct String {
vec: Vec<u8>
}

Stringstr과 달리 할당된 데이터의 소유권을 가지고 있으며 변수에 대한 정보는 스택(stack) 메모리에, 데이터는 힙(heap) 메모리에 저장된다. 예시는 아래 그림과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
                     buffer
/ capacity
/ / length
/ / /
+–––+–––+–––+
stack frame │ • │ 8 │ 6 │ <- my_name: String
+–│–+–––+–––+

[–│–––––––– capacity –––––––––––]

+–V–+–––+–––+–––+–––+–––+–––+–––+
heap │ P │ a │ s │ c │ a │ l │ │ │
+–––+–––+–––+–––+–––+–––+–––+–––+

[––––––– length ––––––––]

[출처]https://blog.thoughtram.io/string-vs-str-in-rust/

만약 문자열에 데이터를 추가한다면 스택 프레임에서는 capacity와 length만 변동하며, 실제 데이터는 힙 메모리에 추가된다.

str

str은 러스트에서 기본적으로 제공하는 데이터 타입 중 하나이며, ‘문자열 리터럴’이라고도 불린다.

NOTE
리터럴(Literal)이란?
리터럴은 변수에 할당되는 데이터 그 자체를 의미한다. 예를 들어 프로그램에서 a = 100 코드는 a라는 변수에 100이라는 값이 할당된다. 여기서 100은 주소나 특정 의미를 내포한 개념이 아니라 그 자체 숫자로의 100을 의미하며, 이런 데이터 값들을 리터럴이라고 한다. 리터럴의 경우 데이터 세그먼트 영역에 저장되기에 불가변적이다. 예시는 아래 그림과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
            my_name: &str
[–––––––––––]
+–––+–––+
stack frame │ • │ 6 │
+–│–+–––+

+––+

preallocated +–V–+–––+–––+–––+–––+–––+
read-only │ P │ a │ s │ c │ a │ l │
memory +–––+–––+–––+–––+–––+–––+

[출처]https://blog.thoughtram.io/string-vs-str-in-rust/

문자열 리터럴이란 문자(char)들의 시퀀스 또는 문자열 슬라이스를 의미한다. 슬라이스란 String의 일부를 참조하는 방식이며, 데이터에 대한 소유권이 없다. (슬라이스에 대해서 더 자세히 알고 싶다면 슬라이스 타입을 참고할 수 있다.)
문자열 리터럴로 변수를 생성하는 방법은 아래와 같다.

1
2
3
4
5
6
// 문자들의 시퀀스
let v_str = "This is str";
let t_str = "This is str";
// 문자열 슬라이스
let string = String::from("This is str");
let slice = &string[0..8];

문자열 리터럴인 변수 ‘v_str’의 타입은 &str으로 데이터 세그먼트에 저장된 값을 참조하는 방식이다. 그렇기 때문에 똑같은 값을 가진 다른 변수 ‘t_str’을 생성하면 동일한 메모리 주소를 가진다는 것을 확인할 수 있다. 반면 String으로 생성된 변수 ‘string’을 문자열 슬라이스로 가져온 ‘slice’는 메모리 값이 다른 것을 확인할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
fn main() {
let v_str = "This is str";
println!("{:?}", v_str);
println!("{:?}", v_str as *const _);
let t_str = "This is str";
println!("{:?}", t_str);
println!("{:?}", t_str as *const _);
let string = String::from("This is str");
let slice = &string[..];
println!("{:?}", slice);
println!("{:?}", slice as *const _);
}
//동일한 메모리 값을 가져온다.
"This is str"
0x55e7e918c141
"This is str"
0x55e7e918c141
//내용은 동일하지만 다른 메모리 값을 가져온다.
"This is str"
0x55e7e9b00ad0

그 이유는 문자열(string)의 경우 힙(heap) 메모리에 데이터를 올려두며 이를 참조해가기 때문이다. 이를 그림으로 나타내면 아래와 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
            my_name: String   last_name: &str
[––––––––––––] [–––––––]
+–––+––––+––––+–––+–––+–––+
stack frame │ • │ 16 │ 13 │ │ • │ 6 │
+–│–+––––+––––+–––+–│–+–––+
│ │
│ +–––––––––+
│ │
│ │
│ [–│––––––– str –––––––––]
+–V–+–––+–––+–––+–––+–––+–––+–V–+–––+–––+–––+–––+–––+–––+–––+–––+
heap │ P │ a │ s │ c │ a │ l │ │ P │ r │ e │ c │ h │ t │ │ │ │
+–––+–––+–––+–––+–––+–––+–––+–––+–––+–––+–––+–––+–––+–––+–––+–––+

[출처]https://blog.thoughtram.io/string-vs-str-in-rust/

비교

아래의 표는 지금까지 살펴봤던 str, String을 비교 및 정리한 것이다.

비교 str String
타입 원시 타입
(Primitive Type)
구조체
소유권 없음 있음
형태 String의 슬라이스 확장가능한 배열
사이즈 컴파일 시 확인 가능 컴파일 시 확인 불가
메모리 데이터 세그먼트에 저장 힙(heap)에 저장
할당 &로 참조하여 할당 바로 할당

&str vs. &String

함수를 작성하다보면 문자열 참조를 파라미터로 받을 때 두 옵션 중에 헷갈릴 수 있다. 아래 예시 코드를 보자.

1
2
3
4
5
6
7
8
9
10
11
fn main() {
let string = String::from("Understanding the String concept?");
let v_str = "This is str";

print_data(&string);
print_data(v_str);
}

fn print_data(data: &str) {
println!("printing data {} ", data);
}

위 코드는 main()에서 ‘string’이라는 String타입의 데이터를 생성하고 이를 문자열 슬라이스로 참조하여 print_data()로 넘겨준다. 러스트에서 문자열 변수의 참조는 자동으로 문자열 슬라이스로 전환한다. 따라서 print_data()은 ‘string’도 받을 수 있고, ‘v_str’도 인자값으로 받을 수 있다.

1
2
3
4
5
6
7
8
9
10
11
fn main() {
let string = String::from("Understanding the String concept?");
let v_str = "This is str";

print_data(&string);
print_data(v_str);
}

fn print_data(data: &String) {
println!("printing data {} ", data);
}

반면 위 코드는 아래와 같은 에러를 출력한다.

1
2
3
4
5
6
7
8
9
10
11
error[E0308]: mismatched types
--> src/main.rs:6:16
|
6 | print_data(v_str);
| ^^^^^ expected struct `String`, found `str`
|
= note: expected reference `&String`
found reference `&str`

For more information about this error, try `rustc --explain E0308`.
error: could not compile `playground` due to previous error

그 이유는 &String&str을 포함하고 있지 않기 때문이다. &String은 힙 메모리에 올라간 데이터를 참조하고자 하기에 문자열 리터럴로 저장된 ‘v_str’은 받지 못한다. 따라서 해당 코드가 작동하려면 .to_string()을 통해 힙 메모리로 올려야 하는데 참조만 할 변수에 사용하기에는 리소스 낭비라고도 볼 수 있다.

참고문헌

이 글은 아래의 블로그들을 참고하여 작성되었다.

https://blog.thoughtram.io/string-vs-str-in-rust/
https://www.becomebetterprogrammer.com/rust-string-vs-str/