카피 유형에 대한 차용 특성의 반대?

Aug 18 2020

나는 본 적이 Borrow소유 한 유형 또는 참조, 예를 들어 모두 동의 기능을 정의하는 데 사용되는 특성 T또는 &T. borrow()Methods를 획득하는 함수 호출된다 &T.

대향 (즉, 허용 함수 있도록 일부 특성 있는가 T또는 &T획득하고 T대) Copy유형?

예를 들면 다음과 같습니다.

use std::borrow::Borrow;

fn foo<T: Borrow<u32>>(value: T) -> u32 {
    *value.borrow()
}

fn main() {
    println!("{}", foo(&5));
    println!("{}", foo(5));
}

이것은 borrow()참조를 얻기 위해 호출 하고 즉시 역 참조됩니다.

T전달 된 경우 값을 복사하고 &T제공된 경우 역 참조하는 다른 구현이 있습니까? 아니면 위의 것이 이런 종류의 글을 쓰는 관용적 방법입니까?

답변

5 trentcl Aug 18 2020 at 14:09

Borrow같은 방식으로 함수에 대한 경계로 실제로 유용하지 않기 때문에에 대한 역 특성은 실제로 없습니다 Borrow. 그 이유는 소유권과 관련이 있습니다.

왜 "역 Borrow"이 덜 유용 Borrow합니까?

참조가 필요한 함수

인수를 참조하기 만하면되는 함수를 고려하십시오.

fn puts(arg: &str) {
    println!("{}", arg);
}

수락 Stringputs데이터의 소유권을 가질 필요가 없기 때문에 여기서는 어리석은 일이지만 수락 &str은 때때로 호출자가 데이터를 필요 이상으로 오래 유지하도록 강요 할 수 있음 을 의미합니다.

{
    let output = create_some_string();
    output.push_str(some_other_string);
    puts(&output);
    // do some other stuff but never use `output` again
} // `output` isn't dropped until here

문제는에 output전달 된 후 필요하지 않으며 puts호출자가 이것을 알고 있지만 puts참조가 필요하므로 output블록이 끝날 때까지 살아 있어야합니다. 분명히 더 많은 블록을 추가하고 때로는를 추가하여 호출자에서 이것을 수정할 수 let있지만 puts호출자 가 정리 책임을 위임 하도록 일반화 할 수도 있습니다 output.

fn puts<T: Borrow<str>>(arg: T) {
    println!("{}", arg.borrow());
}

T: Borrowfor puts를 수락 하면 호출자가 인수를 유지하거나 함수로 이동할지 여부를 유연하게 결정할 수 있습니다.

소유 값이 필요한 함수

이제 실제로 소유권을 가져야하는 함수의 경우를 고려하십시오.

struct Wrapper(String);
fn wrap(arg: String) -> Wrapper {
    Wrapper(arg)
}

이 경우 수용 &str하기 때문에, 바보가 될 것입니다 wrap전화를 할 것입니다 to_owned()그것을. 호출자가 String더 이상 사용하지 않는가 있으면 방금 함수로 이동할 수있는 데이터를 불필요하게 복사합니다. 이 경우 수락 String은 호출자가 복제본을 만들지 또는 기존를 전달할지 결정할 수 있기 때문에 더 유연한 옵션 String입니다. "역 Borrow"특성 을 갖는 arg: String것은 아직 제공하지 않는 유연성을 추가 하지 않습니다.

그러나 String문자열의 여러 가지 종류가 있기 때문에, 항상 가장 인체 공학적인 인수되지 않습니다 : &str, Cow<str>, Box<str>... 우리는 할 수 wrap는 변환 할 수있는 모든 것을 받아 말하면서 조금 더 인체 공학적 intoString.

fn wrap<T: Into<String>>(arg: T) -> Wrapper {
    Wrapper(arg.into())
}

즉 , 리터럴 wrap("hello, world")을 호출하지 않고도 다음과 같이 호출 할 수 있습니다 .to_owned(). 이것은 실제로 유연성이있는 승리 는 아닙니다 . 발신자는 .into()일반성을 잃지 않고 항상 대신 전화를 걸 수 있지만 인체 공학적 승리입니다.

무엇에 대한 Copy유형?

이제 Copy유형 에 대해 물었습니다 . 대부분의 경우 위의 주장이 여전히 적용됩니다. 당신은 같은 함수 작성하는 경우 puts만 필요 &A하여, T: Borrow<A>발신자에 대한보다 유연한 수 있습니다을; wrap전체를 필요 로하는 함수 A의 경우 A. 그러나 Copy유형의 경우 수용 의 인체 공학적 이점 T: Into<A>은 훨씬 덜 명확합니다.

  • 정수 유형의 경우 제네릭이 유형 추론을 엉망으로 만들기 때문에 일반적으로 리터럴을 사용 하는 것이 인체 공학적 이지 않습니다 . 유형에 명시 적으로 주석을 달아야 할 수도 있습니다.
  • &u32구현하지 않기 때문에 Into<u32>그 특정 트릭은 어쨌든 여기서 작동하지 않을 것입니다.
  • 때문에 Copy유형이 소유 한 값으로 쉽게 사용할 수 있습니다, 그것은 처음에 참조로 사용할 덜 일반적입니다.
  • 마지막으로, a &AAwhen 으로 바꾸는 것은 다음 을 추가하는 A: Copy것만큼이나 간단합니다 *. 이 단계를 건너 뛸 수 있다는 것은 대부분의 경우 제네릭을 사용하는 데 따른 복잡성을 상쇄하기에 충분히 설득력이 없을 것입니다.

결론적으로, foo거의 확실하게 수락 value: u32하고 발신자가 그 가치를 얻는 방법을 결정하도록해야합니다.

또한보십시오

  • 메서드가 값의 소유권을 필요로 할 때 값으로 전달하거나 참조로 전달하는 것이 더 일반적입니까?
2 Sunreef Aug 18 2020 at 11:00

가지고있는 기능을 사용하면 u32으로 빌릴 수 있는 또는 유형 만 사용할 수 있습니다 u32.

두 번째 템플릿 인수를 사용하여 함수를보다 일반적으로 만들 수 있습니다.

fn foo<T: Copy, N: Borrow<T>>(value: N) -> T {
    *value.borrow()
}

그러나 이것은 일부 경우에 올바르게 작동하기 위해 유형 주석이 필요하므로 부분적인 해결책 일뿐입니다.

예를 들어 다음과 같이 즉시 작동합니다 usize.

let v = 0usize;
println!("{}", foo(v));

여기에 컴파일러 foo(v)usize.

그러나을 시도 foo(&v)하면 컴파일러는 다른 유형에 대해 여러 특성을 구현할 수 T있기 때문에 올바른 출력 유형을 찾을 수 없다고 불평합니다 . 출력으로 사용할 것을 명시 적으로 지정해야합니다.&TBorrow

let output: usize = foo(&v);