문자열을 결합하는 방법에 대해 알아보겠습니다. + 연산자와 format! 매크로, push_str과 push를 사용해 문자 또는 문자열을 결합할 수 있습니다. 아래에서 하나씩 살펴보겠습니다.
1. + 연산자
왼쪽 피연산자는 반드시 String이어야 하고
오른쪽 피연산자는 &str이어야 합니다.
fn main() { let s1 = String::from("Hello"); let s2 = " World"; let s3 = s1 + s2; // s1은 이동(move)되고, s3가 새 String이 됨 println!("{}", s3); }
⚠️s1은 + 연산 후 사용할 수 없습니다(소유권 이동). 그러나, s2는 s3로 결합된 이후에도 사용할 수 있습니다.
2. format! 매크로 (가장 안전하고 권장)
소유권을 이동하지 않으면서 여러 문자열을 결합 가능.
fn main() { let s1 = String::from("Hello"); let s2 = String::from("World"); let s3 = format!("{} {}", s1, s2); println!("{}", s3); println!("{}", s1); // s1, s2 둘 다 여전히 사용 가능 }
3. push_str / push
String에 직접 이어 붙임.
push_str의 인수는 String이 아니라 &str여야 합니다.
fn main() { let mut s = String::from("Hello"); s.push_str(" World"); // 문자열 이어붙이기 s.push('!'); // 문자(char) 추가 println!("{}", s); }
push_str의 인수로 String을 넣으니 &str이어야 한다고 하면서 에러가 발생합니다.
📌 정리
문자열 결합 → + 연산자(소유권 이동) 또는 format!(안전, 권장)
문자 추가 → push, push_str
문자(char) 타입은 'a'처럼 작은따옴표 사용, 문자열은 "abc"처럼 큰따옴표 사용.
문자열(String, &str) 결합 방법을 비교하는 표
구분
타입
결합 방법
소유권 이동 여부
예제
문자열 결합 ①
String + &str
+ 연산자
왼쪽 String 이동
let s1 = String::from(“Hi”); let s2 = s1 + ” Rust”;
문자열 결합 ②
String + String
불가능(직접은 안 됨)
–
String 둘 다 소유권 문제 → + 사용 시 오른쪽을 &s2로 변경 필요
문자열 결합 ③
여러 문자열
format! 매크로
이동 없음
let s1 = “Hi”; let s2 = “Rust”; let s3 = format!(“{} {}”, s1, s2);
문자열에 덧붙이기
String + &str
push_str()
없음
let mut s = String::from(“Hi”); s.push_str(” Rust”);
문자 추가
String + char
push()
없음
let mut s = String::from(“Hi”); s.push(‘!’);
문자 결합
char + char
직접 불가 → String 변환 후 결합
변환 후 결합
let c1 = ‘A’; let c2 = ‘B’; let s = format!(“{}{}”, c1, c2);
fn main() {
let c1 = 'A';
let c2 = 'B';
let s = c1.to_string() + &c2.to_string(); // char를 String으로 변환 후 이어붙이기
println!("{}", s);
}
Rust에서 array와 vector는 모두 여러 개의 값을 순차적으로 저장하는 자료구조입니다. 하지만 두 타입은 메모리 관리, 크기, 사용 목적 등에서 중요한 차이점이 있습니다. 이 글에서는 각 자료구조의 특징, 사용법, 예제, 그리고 언제 어떤 것을 선택해야 하는지에 대해 자세히 알아보겠습니다.
let slice = &v[1..3]; println!(“{:?}”, slice); => v 벡터에서 1번 인덱스부터 3미만인 2번 인덱스까지 참조 형식으로 가져오는 slice는 [99, 3]이 됩니다. Vector는 일반 포맷인 {}로는 출력이 안되므로 디버그 포맷인 {:?}으로 출력해야 합니다.
let doubled: Vec<i32> = v.iter().map(|x| x * 2).collect(); println!(“{:?}”, doubled); => v 벡터의 요소 들에 2를 곱한 후 새로운 벡터로 변환한 후 doubled에 저장합니다. doubled의 타입도 i32타입의 Vector이므로 디버그 포맷으로 출력하면 [2, 198, 6, 8, 10, 12, 14, 16, 18]가 출력됩니다.
6. 성능 차이의 실제 사례
6.1. 반복문에서의 성능
fn main() { let arr = [1; 1000000]; let vec = vec![1; 1000000];
let mut sum = 0; for i in 0..arr.len() { sum += arr[i]; }
let mut sum2 = 0; for i in 0..vec.len() { sum2 += vec[i]; } }
실행 시간: 배열이 벡터보다 약간 더 빠른 경우가 많습니다.
이유: 배열은 스택에 연속적으로 저장되어 CPU 캐시 효율이 높고, 컴파일러가 최적화를 더 적극적으로 적용할 수 있습니다.
위와 같이 배열의 개수를 1백만개로 숫자가 1인데도 array의 경우 overflow가 발생하므로 1십만으로 바꾸는데, 시간을 체크하는 부분을 추가하고, 천단위 쉼표를 추가하도록 아래와 같이 수정한 후
[Cargo.toml] -num-format 크레이트를 사용하기 위해 필요
[dependencies] num-format = "0.4"
[main.rs]
use std::time::Instant; use num_format::{Locale, ToFormattedString};
fn main() { let arr = [1; 100_000]; let vec = vec![1; 100_000];
// 배열 합계 시간 측정 let start = Instant::now(); let mut sum = 0; for i in 0..arr.len() { sum += arr[i]; } let duration = start.elapsed(); println!("Array sum: {}, elapsed: {:?}", sum.to_formatted_string(&Locale::ko), duration);
// 벡터 합계 시간 측정 let start = Instant::now(); let mut sum2 = 0; for i in 0..vec.len() { sum2 += vec[i]; } let duration = start.elapsed(); println!("Vector sum: {}, elapsed: {:?}", sum2.to_formatted_string(&Locale::ko), duration); }
Instant::now()로 현재 시각을 기록하고, 반복문이 끝난 후 elapsed()로 소요 시간을 구합니다.
for i in 0..arr.len()라고 0부터 배열의 길이전까지 i를 반복하면 sum에 arr[i]를 더하도록 했는데, for i in arr.iter()라고 하고, sum += i;이라고 해도 되는데, iter()를 이용한 것이 훨씬 빠릅니다. 특히 Vector 속도가 많이 빨라졌습니다. Array sum: 100,000, elapsed: 728.6µs Vector sum: 100,000, elapsed: 875.7µs ※ 그런데 매번 속도가 다르기 때문에 위 수치가 절대적인 것은 아닙니다. 어느 때는 Vector가 빠른 경우도 있습니다.
sum 또는 sum2 다음에 num_format의 ToFormattedString(&Locale::ko)를 추가해서 숫자에 천단위마다 쉼표를 추가합니다.
6.2. 크기 변경 및 데이터 추가
배열은 크기가 고정되어 있어, 데이터 추가/삭제가 불가능합니다.
벡터는 push, pop, extend 등으로 동적으로 크기를 조절할 수 있지만, 이 과정에서 메모리 재할당이 발생할 수 있습니다. 대량의 데이터를 추가할 때는 재할당 오버헤드가 성능 저하 요인이 됩니다.
6.3. 벤치마크 및 실제 사용 조언
고정 크기, 빠른 반복/접근이 필요하다면 배열이 유리합니다.
크기가 가변적이거나, 데이터 추가/삭제가 빈번하다면 벡터가 적합합니다.
대용량 데이터 처리에서 벡터는 힙 할당 및 재할당 비용이 있으므로, 성능이 민감한 경우 벡터의 용량을 미리 예약(with_capacity)하는 것이 좋습니다.
※with_capacity란?
Vec::with_capacity는 Rust의 벡터(Vec)를 생성할 때 초기 용량(capacity) 을 미리 지정하는 메서드입니다.
with_capacity(n)은 최소 n개의 요소를 저장할 수 있는 공간을 미리 할당한 빈 벡터를 생성합니다.
이렇게 하면, 벡터에 요소를 추가할 때마다 메모리를 재할당하는 비용을 줄일 수 있어 성능이 향상됩니다.