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


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.