Rust의 정확한 자동 역 참조 규칙은 무엇입니까?


181

나는 Rust와 함께 배우고 / 실험하고 있으며,이 언어에서 발견되는 모든 우아함에 나를 방해하고 완전히 벗어난 것처럼 보이는 하나의 특이성이 있습니다.

Rust는 메소드 호출시 포인터를 자동으로 역 참조합니다. 정확한 동작을 결정하기 위해 몇 가지 테스트를 수행했습니다.

struct X { val: i32 }
impl std::ops::Deref for X {
    type Target = i32;
    fn deref(&self) -> &i32 { &self.val }
}

trait M { fn m(self); }
impl M for i32   { fn m(self) { println!("i32::m()");  } }
impl M for X     { fn m(self) { println!("X::m()");    } }
impl M for &X    { fn m(self) { println!("&X::m()");   } }
impl M for &&X   { fn m(self) { println!("&&X::m()");  } }
impl M for &&&X  { fn m(self) { println!("&&&X::m()"); } }

trait RefM { fn refm(&self); }
impl RefM for i32  { fn refm(&self) { println!("i32::refm()");  } }
impl RefM for X    { fn refm(&self) { println!("X::refm()");    } }
impl RefM for &X   { fn refm(&self) { println!("&X::refm()");   } }
impl RefM for &&X  { fn refm(&self) { println!("&&X::refm()");  } }
impl RefM for &&&X { fn refm(&self) { println!("&&&X::refm()"); } }


struct Y { val: i32 }
impl std::ops::Deref for Y {
    type Target = i32;
    fn deref(&self) -> &i32 { &self.val }
}

struct Z { val: Y }
impl std::ops::Deref for Z {
    type Target = Y;
    fn deref(&self) -> &Y { &self.val }
}


#[derive(Clone, Copy)]
struct A;

impl M for    A { fn m(self) { println!("A::m()");    } }
impl M for &&&A { fn m(self) { println!("&&&A::m()"); } }

impl RefM for    A { fn refm(&self) { println!("A::refm()");    } }
impl RefM for &&&A { fn refm(&self) { println!("&&&A::refm()"); } }


fn main() {
    // I'll use @ to denote left side of the dot operator
    (*X{val:42}).m();        // i32::m()    , Self == @
    X{val:42}.m();           // X::m()      , Self == @
    (&X{val:42}).m();        // &X::m()     , Self == @
    (&&X{val:42}).m();       // &&X::m()    , Self == @
    (&&&X{val:42}).m();      // &&&X:m()    , Self == @
    (&&&&X{val:42}).m();     // &&&X::m()   , Self == *@
    (&&&&&X{val:42}).m();    // &&&X::m()   , Self == **@
    println!("-------------------------");

    (*X{val:42}).refm();     // i32::refm() , Self == @
    X{val:42}.refm();        // X::refm()   , Self == @
    (&X{val:42}).refm();     // X::refm()   , Self == *@
    (&&X{val:42}).refm();    // &X::refm()  , Self == *@
    (&&&X{val:42}).refm();   // &&X::refm() , Self == *@
    (&&&&X{val:42}).refm();  // &&&X::refm(), Self == *@
    (&&&&&X{val:42}).refm(); // &&&X::refm(), Self == **@
    println!("-------------------------");

    Y{val:42}.refm();        // i32::refm() , Self == *@
    Z{val:Y{val:42}}.refm(); // i32::refm() , Self == **@
    println!("-------------------------");

    A.m();                   // A::m()      , Self == @
    // without the Copy trait, (&A).m() would be a compilation error:
    // cannot move out of borrowed content
    (&A).m();                // A::m()      , Self == *@
    (&&A).m();               // &&&A::m()   , Self == &@
    (&&&A).m();              // &&&A::m()   , Self == @
    A.refm();                // A::refm()   , Self == @
    (&A).refm();             // A::refm()   , Self == *@
    (&&A).refm();            // A::refm()   , Self == **@
    (&&&A).refm();           // &&&A::refm(), Self == @
}

( 운동장 )

따라서 다소간 나타납니다.

  • 컴파일러는 메소드 호출에 필요한만큼의 역 참조 연산자를 삽입합니다.
  • &self(참조 별 호출)을 사용하여 선언 된 메소드를 해석 할 때 컴파일러는 다음을 수행합니다.
    • 먼저 하나의 역 참조를 요구합니다. self
    • 그런 다음 정확한 유형의 전화를 시도합니다 self
    • 그런 다음 일치하는 데 필요한만큼의 역 참조 연산자를 삽입합니다.
  • self유형에 대해 (값별 호출)을 사용하여 선언 된 메소드는 유형에 대해 (기준 별 호출)을 T사용하여 선언 되고 도트 연산자의 왼쪽에있는 모든 것에 대한 참조에서 호출 된 것처럼 작동합니다 .&self&T
  • 위의 규칙은 먼저 내장 된 역 참조로 시도되며, 일치하지 않으면 Deref특성이 있는 과부하 가 사용됩니다.

정확한 자동 역 참조 규칙은 무엇입니까? 누구든지 그러한 디자인 결정에 대한 공식적인 근거를 줄 수 있습니까?


나는 좋은 답변을 얻기 위해 Rust subreddit 에 이것을 교차 게시했습니다 !
Shepmaster

더 재미있게 실험을 제네릭으로 반복하고 결과를 비교하십시오.
user2665887

답변:


137

의사 코드는 거의 정확합니다. 이 예에서는 foo.bar()where 메소드 호출이 있다고 가정 foo: T합니다. 내가 사용하는거야 완전한 구문 메소드가 호출되고 입력, 예를 들어 대해 모호로 (FQS을) A::bar(foo)A::bar(&***foo). 나는 임의의 대문자로 된 더미를 쓸 것입니다. 각각은 임의의 유형 / 특성 T입니다. 항상 foo메서드가 호출 되는 원래 변수의 유형입니다 .

알고리즘의 핵심은 다음과 같습니다.

  • "역 참조 단계"에 대해 U (즉, 설정 U = T한 다음 U = *T...)
    1. bar수신자 유형 ( 메소드 의 유형 self)이 U정확히 일치하는 메소드가있는 경우이를 사용하십시오 ( "값별 메소드" )
    2. 그렇지 않으면 하나의 자동 참조 ( 수신 &또는 &mut수신자)를 추가하고 일부 메소드의 수신자가 일치 &U하는 경우이를 사용하십시오 ( "autorefd 메소드" ).

특히, 모든 방법의 "수신기 유형"고려 하지Self 즉, 형질의 유형 impl ... for Foo { fn method(&self) {} }에 대해 생각 &Foo하는 방법을 일치시킬 때,하고 fn method2(&mut self)생각 할 &mut Foo때 일치.

내부 단계에서 유효한 여러 가지 형질 분석법이있는 경우 오류가 발생합니다 (즉, 각 1 또는 2에 유효한 형질 분석법은 0 개 또는 1 개만있을 수 있지만 각각에 대해 하나씩 만 가능합니다). 1부터 시작하여 고유 한 방법이 특성 방법보다 우선합니다. 일치하는 것을 찾지 않고 루프의 끝에 도달하면 오류입니다. Deref루프를 무한대로 만드는 재귀 적 구현 을 갖는 것은 또한 오류입니다 ( "재귀 한계"에 도달합니다).

모호하지 않은 FQS 양식을 작성하는 기능은 일부 경우에 매우 유용하지만 매크로 생성 코드에 대한 합리적인 오류 메시지에 유용하지만 이러한 규칙은 대부분의 상황에서 의미하는 것처럼 보입니다.

하나의 자동 참조 만 추가되므로

  • 바운드가 없으면 모든 유형이 임의의 수의 참조를 가질 수 있기 때문에 상황이 나쁘거나 느려집니다.
  • 하나의 참조를 얻는 &foo것은 foo( foo그 자체 의 주소 인 )에 대한 강력한 연결을 유지 하지만 더 많이 잃기 시작하면 : &&foo스택에있는 임시 변수의 주소가 저장 &foo됩니다.

다음과 같은 유형의 foo.refm()경우 call이 있다고 가정하십시오 foo.

  • X, 우리는 시작 U = X, refm수신기 유형이 &...1 단계가 일치하지 않도록 자동 심판이 우리에게주는 복용, &X그리고 이것은 일치 (와 않는 Self = X호출하므로,)RefM::refm(&foo)
  • &X,로 시작 하여 첫 번째 단계 ()와 U = &X일치 하므로 호출은&selfSelf = XRefM::refm(foo)
  • &&&&&X, 이것은 단계 중 하나와 일치하지 않으므로 (특성이 구현되지 않았 &&&&X거나 또는 &&&&&X) 1을 가져 와서 U = &&&&X1 (with Self = &&&X)을 호출하고 호출은RefM::refm(*foo)
  • Z, 어느 단계와도 일치하지 않으므로 한 번 역 참조되어 get Y에 도달합니다. 또한 일치하지 않으므로 다시 역 참조되어 get X과 일치하지만 1과 일치하지 않지만 자동 참조 후 일치하므로 호출은 RefM::refm(&**foo)입니다.
  • &&A특성이 &A(1) 또는 &&A(2)에 대해 구현되지 않았으므로 1과 일치하지 않으며 1 &A과 일치합니다.Self = A

우리가 가정 foo.m(), 그리고이 A없는 Copy경우, foo유형이 있습니다 :

  • A다음 U = A과 일치하는 self전화가 그래서 직접 M::m(foo)Self = A
  • &A, 1이 일치하지 않고 2. ( 특성을 구현 &A하거나 &&A구현 하지 않음) 도 A일치하지 않으므로을 참조 하지 않습니다. 그러나 일치하지는 않지만 값을 M::m(*foo)가져 와서 A밖으로 이동 foo하므로 오류가 발생합니다.
  • &&A, 1. 일치하지 않지만 자동 참조는를 제공합니다.이 경우 일치 &&&A하는 호출은 M::m(&foo)입니다 Self = &&&A.

(이 답변을 기반으로 코드 , 그리고 합리적으로 가까운 (약간 구식) README하는 것입니다 . 니코 Matsakis,이 대답을 통해 보았다 컴파일러 / 언어,이 부분의 주요 저자.)


15
이 답변은 철저하고 상세하게 보이지만 여름에는 짧고 접근 가능한 규칙이 부족하다고 생각합니다. Shepmaster 는 이러한 의견 중 하나를 다음과 같이 언급했다 . "[deref 알고리즘]은 가능한 한 여러 번 참조하고 ( &&String-> &String-> String-> str) 최대 한 번 참조합니다 ( str-> &str).
Lii

(나는 그 설명이 얼마나 정확하고 완전한지 모르겠다.)
Lii

1
어떤 경우 자동 역 참조가 발생합니까? 메소드 호출의 수신자 표현식에만 사용됩니까? 필드 액세스에도? 오른쪽 할당? 왼손? 기능 매개 변수? 반환 값 표현식?
Lii

1
참고 : 현재 nomicon은이 답변의 정보를 훔쳐서 static.rust-lang.org/doc/master/nomicon/dot-operator.html
SamB

1
강제 변환 (A)이 시도되기 전에 (B)이 시도 후에 시도되거나 (C)이 알고리즘의 모든 단계에서 시도되거나 (D) 다른 것입니까?
haslersn

8

Rust 참조에는 메소드 호출 표현식에 대한 장이 있습니다. 아래에서 가장 중요한 부분을 복사했습니다. 알림 : 우리는 표현에 대해 이야기하고 있습니다. recv.m()여기서 recv"수신자 표현"이라고합니다.

첫 번째 단계는 후보 수신자 유형 목록을 작성하는 것입니다. 수신자 표현식의 유형을 반복적으로 역 참조하고, 발견 된 각 유형을 목록에 추가 한 다음 마지막에 크기가없는 강제 변환을 시도하고, 성공한 경우 결과 유형을 추가하여이를 확보하십시오. 그런 다음 각 후보에 대해 및 바로 다음에 목록에 T추가하십시오 .&T&mut TT

수신기 입력이있는 경우 예를 들어, Box<[i32;2]>다음 후보 종류의 것 Box<[i32;2]>, &Box<[i32;2]>, &mut Box<[i32;2]>, [i32; 2], (비 참조하여) &[i32; 2], &mut [i32; 2], [i32](크기를 지정하지 않은 강제함으로써) &[i32], 그리고 마지막 &mut [i32].

그런 다음 각 후보 T유형에 대해 다음 위치에서 해당 유형의 수신자를 사용하여 가시적 인 메소드를 검색하십시오.

  1. T의 고유 메소드 ( T[¹] 에 직접 구현 된 메소드 ).
  2. 에 의해 구현 된 가시적 특성에 의해 제공되는 모든 방법 T. [...]

( [¹]에 대한 참고 사항 : 실제로이 문구가 잘못되었다고 생각합니다. 문제를 열었습니다 . 괄호 안의 해당 문장을 무시하십시오.)


코드에서 몇 가지 예를 자세히 살펴 보겠습니다! 예를 들어, "크기가없는 강제"및 "고유 방법"에 대한 부분은 무시해도됩니다.

(*X{val:42}).m(): 수신자 표현식의 유형은 i32입니다. 다음 단계를 수행하십시오.

  • 후보 수신자 유형 목록 작성 :
    • i32 역 참조 할 수 없으므로 이미 1 단계를 완료했습니다. [i32]
    • 다음으로 &i32and 를 추가 &mut i32합니다. 명부:[i32, &i32, &mut i32]
  • 각 후보 수신자 유형에 대한 메소드 검색 :
    • 우리 <i32 as M>::m는 수신자 타입을 가진 것을 발견한다 i32. 그래서 우리는 이미 끝났습니다.


지금까지는 너무 쉽습니다. 이제 더 어려운 예를 보자 (&&A).m(). 수신자 표현식의 유형은 &&A입니다. 다음 단계를 수행하십시오.

  • 후보 수신자 유형 목록 작성 :
    • &&A를 역 참조 할 수 있으므로 &A목록에 추가합니다. &A다시 역 참조 될 수 있으므로 A목록 에도 추가 됩니다. A역 참조 할 수 없으므로 중지합니다. 명부:[&&A, &A, A]
    • 다음으로 T목록의 각 유형 에 대해을 추가 &T하고 &mut T바로 다음에 추가 합니다 T. 명부:[&&A, &&&A, &mut &&A, &A, &&A, &mut &A, A, &A, &mut A]
  • 각 후보 수신자 유형에 대한 메소드 검색 :
    • 수신자 유형의 메소드가 없습니다 &&A 목록에서 다음 유형으로 이동합니다.
    • <&&&A as M>::m실제로 수신자 유형을 가진 메소드 를 찾습니다 &&&A. 그래서 우리는 끝났습니다.

다음은 모든 예에 대한 후보 수신자 목록입니다. 동봉 된 유형은 ⟪x⟫"원한"유형, 즉 피팅 방법을 찾을 수있는 첫 번째 유형입니다. 또한 목록의 첫 번째 유형은 항상 수신자 표현식의 유형이라는 것을 기억하십시오. 마지막으로 세 줄로 목록의 형식을 지정했지만 형식은 다음과 같습니다.이 목록은 단순 목록입니다.

  • (*X{val:42}).m()<i32 as M>::m
    [i32, &i32, &mut i32]
  • X{val:42}.m()<X as M>::m
    [⟪X⟫, &X, &mut X, 
     i32, &i32, &mut i32]
  • (&X{val:42}).m()<&X as M>::m
    [&X⟫, &&X, &mut &X, 
     X, &X, &mut X, 
     i32, &i32, &mut i32]
  • (&&X{val:42}).m()<&&X as M>::m
    [&&X⟫, &&&X, &mut &&X, 
     &X, &&X, &mut &X, 
     X, &X, &mut X, 
     i32, &i32, &mut i32]
  • (&&&X{val:42}).m()<&&&X as M>::m
    [&&&X⟫, &&&&X, &mut &&&X, 
     &&X, &&&X, &mut &&X, 
     &X, &&X, &mut &X, 
     X, &X, &mut X, 
     i32, &i32, &mut i32]
  • (&&&&X{val:42}).m()<&&&X as M>::m
    [&&&&X, &&&&&X, &mut &&&&X,&&&X⟫, &&&&X, &mut &&&X, 
     &&X, &&&X, &mut &&X, 
     &X, &&X, &mut &X, 
     X, &X, &mut X, 
     i32, &i32, &mut i32]
  • (&&&&&X{val:42}).m()<&&&X as M>::m
    [&&&&&X, &&&&&&X, &mut &&&&&X, 
     &&&&X, &&&&&X, &mut &&&&X,&&&X⟫, &&&&X, &mut &&&X, 
     &&X, &&&X, &mut &&X, 
     &X, &&X, &mut &X, 
     X, &X, &mut X, 
     i32, &i32, &mut i32]


  • (*X{val:42}).refm()<i32 as RefM>::refm
    [i32,&i32, &mut i32]
  • X{val:42}.refm()<X as RefM>::refm
    [X,&X⟫, &mut X, 
     i32, &i32, &mut i32]
  • (&X{val:42}).refm()<X as RefM>::refm
    [&X⟫, &&X, &mut &X, 
     X, &X, &mut X, 
     i32, &i32, &mut i32]
  • (&&X{val:42}).refm()<&X as RefM>::refm
    [&&X⟫, &&&X, &mut &&X, 
     &X, &&X, &mut &X, 
     X, &X, &mut X, 
     i32, &i32, &mut i32]
  • (&&&X{val:42}).refm()<&&X as RefM>::refm
    [&&&X⟫, &&&&X, &mut &&&X, 
     &&X, &&&X, &mut &&X, 
     &X, &&X, &mut &X, 
     X, &X, &mut X, 
     i32, &i32, &mut i32]
  • (&&&&X{val:42}).refm()<&&&X as RefM>::refm
    [&&&&X⟫, &&&&&X, &mut &&&&X, 
     &&&X, &&&&X, &mut &&&X, 
     &&X, &&&X, &mut &&X, 
     &X, &&X, &mut &X, 
     X, &X, &mut X, 
     i32, &i32, &mut i32]
  • (&&&&&X{val:42}).refm()<&&&X as RefM>::refm
    [&&&&&X, &&&&&&X, &mut &&&&&X,&&&&X⟫, &&&&&X, &mut &&&&X, 
     &&&X, &&&&X, &mut &&&X, 
     &&X, &&&X, &mut &&X, 
     &X, &&X, &mut &X, 
     X, &X, &mut X, 
     i32, &i32, &mut i32]


  • Y{val:42}.refm()<i32 as RefM>::refm
    [Y, &Y, &mut Y,
     i32,&i32, &mut i32]
  • Z{val:Y{val:42}}.refm()<i32 as RefM>::refm
    [Z, &Z, &mut Z,
     Y, &Y, &mut Y,
     i32,&i32, &mut i32]


  • A.m()<A as M>::m
    [⟪A⟫, &A, &mut A]
  • (&A).m()<A as M>::m
    [&A, &&A, &mut &A,
     ⟪A⟫, &A, &mut A]
  • (&&A).m()<&&&A as M>::m
    [&&A,&&&A⟫, &mut &&A,
     &A, &&A, &mut &A,
     A, &A, &mut A]
  • (&&&A).m()<&&&A as M>::m
    [&&&A⟫, &&&&A, &mut &&&A,
     &&A, &&&A, &mut &&A,
     &A, &&A, &mut &A,
     A, &A, &mut A]
  • A.refm()<A as RefM>::refm
    [A,&A⟫, &mut A]
  • (&A).refm()<A as RefM>::refm
    [&A⟫, &&A, &mut &A,
     A, &A, &mut A]
  • (&&A).refm()<A as RefM>::refm
    [&&A, &&&A, &mut &&A,&A⟫, &&A, &mut &A,
     A, &A, &mut A]
  • (&&&A).refm()<&&&A as RefM>::refm
    [&&&A,&&&&A⟫, &mut &&&A,
     &&A, &&&A, &mut &&A,
     &A, &&A, &mut &A,
     A, &A, &mut A]
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.