📝

String vs &str — Ruby에는 없는 문자열의 두 얼굴

소유하는 문자열과 빌리는 문자열의 차이

Ruby에서 문자열은 간단하다. String 하나. 만들고, 바꾸고, 넘기면 된다. Rust에서는 문자열이 두 종류다.

String — 소유하는 문자열

let s = String::from("hello");
let s = "hello".to_string();  // 같은 결과

힙에 할당된다. 크기가 변할 수 있다. Ruby의 일반 String과 가장 비슷하다.

&str — 빌리는 문자열

let s: &str = "hello";  // 문자열 리터럴은 항상 &str

어딘가에 있는 문자열 데이터의 참조(슬라이스)다. 크기를 바꿀 수 없다. Ruby의 frozen string에 가깝다.

언제 뭘 쓰나

함수 인자로 받을 때 → &str

// ✅ 좋음 — String도 &str도 받을 수 있다
fn greet(name: &str) {
    println!("Hello, {}!", name);
}
greet("world");           // &str 전달
greet(&my_string);        // String → &str 자동 변환

// ❌ 불필요하게 제한적
fn greet(name: String) {  // String만 받음
    println!("Hello, {}!", name);
}

구조체 필드에 저장할 때 → String

struct User {
    name: String,  // 소유해야 하니까 String
}

변환

// &str → String
let owned: String = "hello".to_string();
let owned: String = String::from("hello");
let owned: String = "hello".to_owned();

// String → &str
let borrowed: &str = &owned;
let borrowed: &str = owned.as_str();

문자열 연결 — Ruby보다 귀찮다

Ruby의 "Hello, " + name이나 "Hello, #{name}"은 간단하다. Rust는 좀 다르다.

// format! — Ruby의 "#{}"/sprintf에 해당
let msg = format!("Hello, {}!", name);

// + 연산자 — 왼쪽이 String이어야 함
let msg = String::from("Hello, ") + name;

// push_str — String에 &str 추가
let mut msg = String::from("Hello, ");
msg.push_str(name);

대부분 format!을 쓰면 된다. Ruby의 문자열 보간과 가장 비슷하다.

왜 두 종류인가

성능과 소유권. &str은 복사 없이 기존 문자열 데이터를 가리킨다. 함수에 문자열을 넘길 때 힙 할당 없이 참조만 전달할 수 있다. 이게 Rust가 빠른 이유 중 하나.

핵심 포인트

1

String = 힙 할당, 가변 / &str = 참조, 불변

2

함수 인자는 &str로 받는 게 관례 — String도 자동 변환됨

3

구조체 필드는 String — 소유권이 필요하니까

4

format!()이 Ruby의 문자열 보간(#{})에 가장 가깝다

장점

  • 불필요한 문자열 복사를 &str로 회피 — 성능 이점
  • String과 &str의 구분이 소유권 이해의 좋은 입구

단점

  • \"hello\".to_string() 같은 변환이 번거롭다
  • 구조체에 &str을 넣으려면 라이프타임 명시가 필요 — 초보자 킬러

사용 사례

API 응답 파싱 — JSON에서 꺼낸 문자열 처리 설정 파일 읽기 — 파일 내용을 String으로, 파싱 중 &str로

참고 자료