목차
- 인자로 &String보다 &str을 사용하라
- Implicit Deref Coercion
- 반복문에서 함수를 호출할 때
인자로 &String보다 &str을 사용하라 - Rust 프로그래밍
Rust에서는 함수나 메서드의 인자 타입으로 &String
보다 &str
을 쓰는 것이 여러모로 더 좋습니다. 우리가 함수나 메서드를 작성할 때, 문자열을 인자로 받는 것은 흔히 볼 수 있는 코드인데요. Rust에서 문자열을 담기 위한 타입은 크게 두 가지로 String
타입과 &
연사자와 함께 사용되는 str
타입이 있습니다. 스트링 슬라이스 혹은 슬라이스 타입으로 불리는 &str은 String과 다르게 값을 소유할 수 없고, String 데이터에 대해 단순히 view를 제공해주는 타입입니다.
그러면, 왜 인자 타입으로 &String보다 &str을 사용하는 것이 좋을까요? 아래 예제에서 print_length 함수는 &String 타입을 인자로 받아 문자열 길이를 출력하는 함수입니다. female_name은 String 타입, male_name은 &str 타입으로 만들고 두 변수를 print_length 함수에 인자로 넘겨 보겠습니다.
fn print_length(name: &String) -> () {
println!("length : {}", name.len());
}
fn main() {
let female_name = "Alice".to_string();
let male_name = "Bob";
print_length(&female_name);
print_length(male_name);
}
네 당연하게도 이 예제 코드는 컴파일되지 않는데요. 이유는 main 함수의 마지막 라인에 있습니다. print_length 함수가 요구하는 인자 타입은 &String인데 &str 타입의 male_name을 인자로 넘기려하고 있기 때문입니다.
그러면 이번에는 &String 타입이 아닌 &str 타입을 인자로 받도록 print_length 함수의 시그니처를 변경해 보겠습니다.
fn print_length(name: &str) -> () {
println!("length : {}", name.len());
}
fn main() {
let female_name = "Alice".to_string();
let male_name = "Bob";
print_length(&female_name);
print_length(male_name);
}
이 코드는 이제 컴파일에 성공합니다. 여기서 우리가 알 수 있는 것은 함수나 메서드의 인자 타입으로 &String을 사용할 때와 &str 타입을 사용할 때 다르게 동작한다는 것입니다.
즉, &String을 인자 타입으로 사용했을 때는 &String 타입만 받을 수 있지만, &str을 인자 타입으로 사용했을 때는 &String과 &str 타입 모두를 받을 수 있습니다. 이것은 역참조 강제(Deref Coercion)와 관련이 있는데요. 이는 아래에서 좀 더 자세히 알아보겠습니다.
Implicit Deref Coercion
위에서 인자 타입이 &str 인 함수가 &String 타입까지 받을 수 있는 이유는 암묵적 역참조 강제가 일어나기 때문입니다. 이 역참조 강제는 Deref Trait을 구현한 타입에만 가능합니다.
아래는 String 타입이 Deref Trait 구현하고 있는 코드입니다.
만약 &String처럼 Deref Trait을 구현한 타입을 인자로 넘긴다면, 컴파일러는 함수의 인자 타입과 맞는 타입을 찾을 때까지 이 deref 함수를 반복해서 호출하는 것입니다. 그리고 이는 컴파일 타임에 이루어지기 때문에 런타임 성능에 영향을 미치지 않습니다.
그러면, 다른 타입에 Deref 트레잇을 구현해보겠습니다. 아래 예제 String 타입의 name을 갖고 있는 Person struct를 정의하고 name의 참조자를 리턴하도록 Deref 트레잇을 구현한 코드입니다.
struct Person<T> {
name: T
}
impl<T> Deref for Person<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.name
}
}
이제 Person 타입의 변수를 하나 만들고 이를 print_length 함수의 인자로 넘겨보겠습니다.
fn main() {
let alice = Person {
name: String::from("Alice"),
};
print_length(&alice);
}
이 코드는 문제없이 컴파일에 성공합니다.
앞서 설명했듯이, 컴파일러는 Person 타입이 Deref 트레잇을 구현했고, &str을 찾을 수 있다는 사실을 알고 있습니다.
따라서, 아래처럼 암묵적 역참조 강제 과정을 반복해서 &str을 찾아낸 것입니다.
&Person -> &String -> &str
다음으로 &str을 사용하는 것이 좋은 또 다른 케이스에 대해 알아보겠습니다.
반복문에서 함수를 호출할 때
아래는 반복문 내에서 print_length를 호출하는 코드의 한 예입니다.
fn main() {
let names = "Alice Bob".to_string();
for name in names.split(" ") {
print_length(name);
}
}
names 변수가 갖고 있는 이름들을 공백을 기준으로 잘라서 처리하는데요. 이때, name의 타입은 &str이고 print_length 함수는 이를 처리할 수 있습니다. 만약, print_length 함수가 &String 타입을 인자로 받는다면 name의 값을 소유하기 위한 변수를 새로 만들거나 하는 등의 처리가 필요하게 됩니다. 이는 굉장히 비효율적인 작업이 될 것입니다.
이번 글에서는 함수나 메서드의 인자로 &String 보다 &str을 사용했을 때, 코드를 좀 더 유연하게 작성할 수 있다는 내용에 대해 알아봤는데요. 다만, 이는 보통의 케이스를 설명하는 것으로 상황에 맞게 적절히 판단하는 것이 좋겠습니다.
References
'Dev > Language' 카테고리의 다른 글
tonic을 이용해 grpc 통신을 구현해 보자 - Rust 프로그래밍 (0) | 2023.03.20 |
---|---|
impl Trait과 Box<dyn Trait> - Rust 프로그래밍 (1) | 2022.12.19 |
Cell 타입과 RefCell 타입 - Rust 프로그래밍 (1) | 2022.11.26 |
Rc 타입과 Weak 타입 - Rust 프로그래밍 (4) | 2022.10.29 |
소유권(Ownership)과 원시 타입 - Rust 프로그래밍 (0) | 2022.08.29 |