Які точні правила автоматичного перенаправлення Руста?


182

Я вчусь / експериментую з Рустом, і в тій елегантності, яку я знаходжу в цій мові, є одна особливість, яка мене бентежить і здається абсолютно не на місці.

Іржа автоматично вказує на перенаправлення під час здійснення викликів методів. Я зробив кілька тестів, щоб визначити точну поведінку:

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(call-by-reference):
    • Спочатку намагається закликати до одноразового відхилення self
    • Потім намагається зателефонувати за точним типом self
    • Потім спробуйте вставити стільки операторів з відновлення, скільки потрібно для відповідності
  • Методи, оголошені з використанням self(call-by-value) для типу, Tповодяться так, ніби вони були оголошені, використовуючи &self(call-by-reference) для типу, &Tі викликали посилання на те, що знаходиться зліва від оператора крапки.
  • Вищезазначені правила спочатку спробують із сирої вбудованої перенаправлення, і якщо немає відповідності, використовується перевантаження з Derefознакою.

Які точні правила автоматичної перенаправлення? Чи може хтось дати якесь офіційне обгрунтування такого дизайнерського рішення?


1
Я переклав це на subreddit Руста в надії отримати хороші відповіді!
— Шепмайстер

Для додаткової забави спробуйте повторити експеримент у генериці та порівняйте результати.
— користувач2665887

Відповіді:


137

Ваш псевдо-код є майже правильним. Для цього прикладу, припустимо, у нас був метод виклику foo.bar()де foo: T. Я буду використовувати повністю кваліфікований синтаксис (FQS), щоб бути однозначним щодо того, який тип викликається методом, наприклад, A::bar(foo)або A::bar(&***foo). Я просто збираюся написати купу випадкових великих літер, кожна з них - це лише якийсь довільний тип / ознака, за винятком того, що Tце завжди тип вихідної змінної foo, за допомогою якої викликається метод.

Основою алгоритму є:

  • Для кожного "кроку відновлення" U (тобто встановити, U = Tа потім U = *T, ...)
    1. якщо є метод, barде тип приймача (тип selfметоду) Uточно відповідає, використовуйте його ( "методом значення" )
    2. в іншому випадку додайте один автоматичний перегляд (візьміть &або &mutприймач), і, якщо приймач якогось методу відповідає &U, використовуйте його ( "метод авторефренда" )

Примітно, що всі вважає «тип приймача» методу, а НЕ на Selfтип ознаки, тобто impl ... for Foo { fn method(&self) {} }думає про те, &Fooколи відповідний метод, і fn method2(&mut self)буде думати про &mut Fooсопрягая.

Це помилка, якщо на внутрішніх кроках колись існує чимало методів ознаки (тобто може бути лише нуль або один метод ознак, дійсний у кожному з 1. або 2. Але може бути один дійсний для кожного: один з 1 буде взято перше), а притаманні методи мають перевагу над типовими. Це також помилка, якщо ми дістаємося до кінця циклу, не знайшовши нічого, що відповідає. Також помилкою є рекурсивні Derefреалізації, які роблять цикл нескінченним (вони досягають "межі рекурсії").

Ці правила, здається, означають, що я маю на увазі в більшості обставин, хоча можливість писати однозначну форму FQS дуже корисна в деяких крайніх випадках, а також для розумних повідомлень про помилки для макрогенерованого коду.

Додано лише одну автопосилання

  • якщо не було обмежених, справи стають поганими / повільними, оскільки кожен тип може мати довільну кількість посилань
  • Прийняття однієї посилання &fooзберігає міцний зв’язок з foo(це fooсама по собі адреса ), але прийняття більше починає її втрачати: &&fooце адреса якоїсь тимчасової змінної на стеку, який зберігається &foo.

Приклади

Припустимо, у нас є дзвінок foo.refm(), якщо він fooмає тип:

  • X, То ми починаємо з U = X, refmмає тип приймача &..., так що крок 1 не відповідає, з автоматичним вих дає нам &X, і це робить матч (з Self = X), тому викликRefM::refm(&foo)
  • &X, починається з U = &X, що відповідає &selfна першому кроці (з Self = X), і тому дзвінок єRefM::refm(foo)
  • &&&&&X, це не відповідає жодному кроку (ознака не реалізована для &&&&Xабо &&&&&X), тому ми переходимо один раз, щоб отримати U = &&&&X, який відповідає 1 (з Self = &&&X), і викликRefM::refm(*foo)
  • Z, не відповідає жодному кроку, тому його одноразово відміняють, щоб отримати Y, що також не збігається, тож знову відміняється, щоб отримати X, що не відповідає 1, але відповідає після автоперевірки, тому дзвінок є RefM::refm(&**foo).
  • &&A, 1. не збігається і не відповідає 2. оскільки ознака не реалізована для &A(для 1) або &&A(для 2), тому вона буде віднесена до &A, що відповідає 1., з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.

(Ця відповідь заснована на коді і є досить близькою до (трохи застарілої) ПРОЧИТАННЯ . Ніко Мацакіс, головний автор цієї частини компілятора / мови, також переглянув цю відповідь.)


15
Ця відповідь видається вичерпною та детальною, але я думаю, що їй не вистачає коротких та доступних літніх правил. Один із таких літніх дається в цьому коментарі Shepmaster : "Він [алгоритм deref] дерефікує якомога більше разів ( &&String-> &String-> String-> str), а потім посилається на максимум один раз ( str-> &str)".
— Лій

(Я не знаю, наскільки точним і повним я є це пояснення.)
— Лій

1
У яких випадках відбувається автоматичне перенаправлення? Чи використовується він лише для виразу приймача для виклику методу? Для доступу до поля також? Присвоєння правої сторони? Лівосторонні сторони? Параметри функції? Вирази повернення значення?
— Лій

1
Примітка: В даний час у номіконі є нотатка TODO, щоб викрасти інформацію з цієї відповіді та записати її на static.rust-lang.org/doc/master/nomicon/dot-operator.html
— SamB

1
Чи намагався примус (A) перед цим, або (B) намагався після цього, або (C) намагався на кожному кроці цього алгоритму, або (D) щось інше?
— haslersn

8

У посиланні на Руст є розділ про вираження виклику методу . Я скопіював найважливішу частину нижче. Нагадування: мова йде про вираз recv.m(), де recvвнизу називається "вираз приймача".

Перший крок - скласти список типів приймачів-кандидатів. Отримайте це шляхом багаторазового перенаправлення типу виразу приймача, додаючи кожен тип, що зустрічається до списку, а потім, нарешті, спробу невстановленого примусу в кінці, і додаючи тип результату, якщо це успішно. Потім для кожного кандидата Tдодайте &Tта &mut Tдо списку одразу після T.

Наприклад, якщо приймач має тип 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]
    • Далі додаємо &i32і &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]
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.