String (& String), Vec (& Vec) 또는 Box (& Box)에 대한 참조를 함수 인수로 받아들이지 않는 이유는 무엇입니까?


127

&String인수로 사용하는 Rust 코드를 작성했습니다 .

fn awesome_greeting(name: &String) {
    println!("Wow, you are awesome, {}!", name);
}

나는 또한에 대한 참조를 취 코드를 작성했습니다 Vec또는 Box:

fn total_price(prices: &Vec<i32>) -> i32 {
    prices.iter().sum()
}

fn is_even(value: &Box<i32>) -> bool {
    **value % 2 == 0
}

그러나 이렇게하는 것은 좋은 생각이 아니라는 피드백을 받았습니다. 왜 안돼?

답변:


162

TL; DR : 하나 대신 사용할 수 있습니다 &str, &[T]또는 &T보다 일반적인 코드를 허용 할 수 있습니다.


  1. a String또는 a 를 사용하는 주된 이유 중 하나 Vec는 용량을 늘리거나 줄일 수 있기 때문입니다. 그러나 변경 불가능한 참조를 수락하면 Vec또는 에서 이러한 흥미로운 메서드를 사용할 수 없습니다 String.

  2. 을 수락 &String, &Vec또는 &Box도 것은 필요 당신이 함수를 호출하기 전에 힙에 할당되는 인수. a를 &str허용하면 문자열 리터럴 (프로그램 데이터에 저장 됨)을 허용하고 &[T]or를 &T허용하면 스택 할당 배열 또는 변수를 허용합니다. 불필요한 할당은 성능 손실입니다. 이는 일반적으로 테스트 또는 메서드에서 이러한 메서드를 호출하려고 할 때 즉시 노출됩니다 main.

    awesome_greeting(&String::from("Anna"));
    total_price(&vec![42, 13, 1337])
    is_even(&Box::new(42))
  3. 또 다른 성능 고려 사항은이다 &String, &Vec그리고 &Box당신이를 역 참조 할 필요가 같은 간접 불필요한 레이어를 소개 &String를 얻기 위해 String다음에서 결국에 두 번째 역 참조를 수행합니다 &str.

대신 문자열 슬라이스 ( &str), 슬라이스 ( &[T]) 또는 참조 ( &T) 만 허용해야합니다 . (A)는 &String, &Vec<T>또는 &Box<T>자동으로 강제 변환됩니다 &str, &[T]또는 &T각각.

fn awesome_greeting(name: &str) {
    println!("Wow, you are awesome, {}!", name);
}
fn total_price(prices: &[i32]) -> i32 {
    prices.iter().sum()
}
fn is_even(value: &i32) -> bool {
    *value % 2 == 0
}

이제 더 광범위한 유형 세트로 이러한 메소드를 호출 할 수 있습니다. 예를 들어 awesome_greeting는 문자열 리터럴 ( "Anna") 또는 할당 된 String. total_price배열 ( &[1, 2, 3])에 대한 참조 또는 할당 된 Vec.


추가하거나에서 항목을 제거하려는 경우 String또는 Vec<T>, 당신은 걸릴 수 변경 가능한 참조 ( &mut String또는 &mut Vec<T>) :

fn add_greeting_target(greeting: &mut String) {
    greeting.push_str("world!");
}
fn add_candy_prices(prices: &mut Vec<i32>) {
    prices.push(5);
    prices.push(25);
}

특히 조각을 위해, 당신은 또한 받아 들일 수 &mut [T]또는 &mut str. 이렇게하면 슬라이스 내부의 특정 값을 변경할 수 있지만 슬라이스 내부의 항목 수는 변경할 수 없습니다 (즉, 문자열에 대해 매우 제한됨).

fn reset_first_price(prices: &mut [i32]) {
    prices[0] = 0;
}
fn lowercase_first_ascii_character(s: &mut str) {
    if let Some(f) = s.get_mut(0..1) {
        f.make_ascii_lowercase();
    }
}

5
방법에 대한 TL; DR 처음에? 이 대답은 이미 다소 깁니다. " &str기능 감소없이 더 일반적입니다 (예 : 제한이 적음) "와 같은 것이 있습니까? 또한 포인트 3은 종종 제가 생각하는 그다지 중요하지 않습니다. 일반적으로 Vecs와 Strings는 스택에 있으며 종종 현재 스택 프레임 근처에 있습니다. 스택은 일반적으로 뜨겁고 역 참조는 CPU 캐시에서 제공됩니다.
Lukas Kalbertodt

3
@Shepmaster : 할당 비용과 관련하여 필수 할당에 대해 이야기 할 때 하위 문자열 / 슬라이스의 특정 문제를 언급 할 가치가 있습니다. total_price(&prices[0..4])슬라이스에 새 벡터를 할당 할 필요가 없습니다.
Matthieu M.

4
이것은 훌륭한 대답입니다. 방금 Rust를 시작했을 때 a를 사용해야 할 때 &str이유를 파악하는 데 묶여있었습니다 (Python에서 왔기 때문에 일반적으로 유형을 명시 적으로 다루지 않습니다). 완벽하게 그까지 모두 클리어
C.Nivs

2
매개 변수에 대한 멋진 팁. 한 가지 의심이 필요합니다. "& String, & Vec 또는 & Box를 수락하려면 메서드를 호출하기 전에 할당이 필요합니다." ... 왜 그렇게합니까? 이 내용을 자세히 읽을 수있는 문서의 부분을 지적 해 주시겠습니까? (저는 초심자입니다). 또한 반환 유형에 대한 유사한 팁을 얻을 수 있습니까?
Nawaz

2
추가 할당이 필요한 이유에 대한 정보가 없습니다. 문자열은 힙에 저장됩니다. & String을 인수로 받아 들일 때 Rust가 힙 공간을 가리키는 스택에 저장된 포인터를 전달하지 않는 이유는 & String을 전달하는 데 추가 할당이 필요한 이유를 이해하지 못합니다. 슬라이스는 힙 공간을 가리키는 스택에 저장된 포인터를 보내야합니까?
cjohansson

22

뿐만 아니라 Shepmaster의 대답 , 또 다른 이유는 수락 &str(유사 &[T]등) 때문에 다른 유형의 모든입니다 외에 String 하고 &str도 만족 것을 Deref<Target = str>. 가장 주목할만한 예 중 하나는 Cow<str>소유 또는 차용 데이터를 처리하는지 여부에 대해 매우 유연하게 할 수있는입니다.

당신이 가지고 있다면:

fn awesome_greeting(name: &String) {
    println!("Wow, you are awesome, {}!", name);
}

하지만를 사용하여 호출 Cow<str>해야합니다.이 작업을 수행해야합니다.

let c: Cow<str> = Cow::from("hello");
// Allocate an owned String from a str reference and then makes a reference to it anyway!
awesome_greeting(&c.to_string());

인수 유형을로 변경하면 다음 과 같이 불필요한 할당없이 원활하게 &str사용할 수 있습니다 .CowString

let c: Cow<str> = Cow::from("hello");
// Just pass the same reference along
awesome_greeting(&c);

let c: Cow<str> = Cow::from(String::from("hello"));
// Pass a reference to the owned string that you already have
awesome_greeting(&c);

수락 &str하면 함수를보다 균일하고 편리하게 호출 할 수 있으며 이제 "가장 쉬운"방법도 가장 효율적입니다. 이 예제는 Cow<[T]>등에서 도 작동합니다 .

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.