В чем разница между & Trait и impl Trait при использовании в качестве аргументов метода? - PullRequest
0 голосов
/ 13 декабря 2018

В моем проекте до сих пор я использовал много черт, чтобы разрешить насмешку / заглушку в модульных тестах для внедренных зависимостей.Однако одна деталь того, что я делаю до сих пор, кажется настолько подозрительной, что я удивляюсь, что она даже компилируется.Я волнуюсь, что происходит что-то опасное, что я не вижу или не понимаю.Он основан на разнице между этими двумя сигнатурами метода:

fn confirm<T>(subject: &MyTrait<T>) ...
fn confirm<T>(subject: impl MyTrait<T>) ...

Я только что обнаружил синтаксис impl ... в аргументах метода, и это похоже на единственный документированный способ сделать это, но мои тесты проходят с использованиемуже другой путь, к которому я пришел с помощью интуиции, основанной на том, как Go решает ту же проблему (размер аргумента метода во время компиляции, когда аргумент может быть любым реализатором интерфейса, и ссылки могут прийти на помощь).

В чем разница между этими двумя?И почему им обоим позволено?Они оба представляют законные варианты использования, или мой ссылочный синтаксис (&MyTrait<T>) является строго худшей идеей?

Ответы [ 2 ]

0 голосов
/ 13 декабря 2018

Они разные и служат разным целям.И то, и другое полезно, и в зависимости от обстоятельств один или другой может быть лучшим выбором.

Первый случай, &MyTrait<T>, предпочтительно пишется &dyn MyTrait<T> в современном Rust.Это так называемый объект черты .Ссылка указывает на любой тип, реализующий MyTrait<T>, и вызовы методов отправляются динамически во время выполнения.Чтобы сделать это возможным, ссылка на самом деле является толстым указателем;Помимо указателя на объект он также хранит указатель на таблицу виртуальных методов типа объекта, чтобы обеспечить динамическую диспетчеризацию.Если фактический тип вашего объекта становится известным только во время выполнения, это единственная версия, которую вы можете использовать, поскольку в этом случае вам нужно использовать динамическую диспетчеризацию.Недостатком подхода является то, что существуют затраты времени выполнения, и что он работает только для черт, объектно-безопасных .

Второй случай, impl MyTrait<T>, обозначает реализацию любого типаMyTrait<T> еще раз, но в этом случае точный тип должен быть известен во время компиляции.Прототип

fn confirm<T>(subject: impl MyTrait<T>);

эквивалентен

fn confirm<M, T>(subject: M)
where
    M: MyTrait<T>;

Для каждого типа M, используемого в вашем коде, компилятор создает отдельную версию confim в двоичном файле,и вызовы методов отправляются статически во время компиляции.Эта версия предпочтительна, если все типы известны во время компиляции, поскольку вам не нужно платить за динамическую диспетчеризацию конкретным типам затрат времени выполнения.

Другое отличие двух прототипов заключается в том, что первая версия принимаетsubject по ссылке, в то время как вторая версия использует передаваемый аргумент. Однако это не является концептуальным отличием - хотя первую версию нельзя записать для использования объекта, вторую версию легко написать для принятия subject по ссылке:

fn confirm<T>(subject: &impl MyTrait<T>);

Учитывая, что вы ввели черты для облегчения тестирования, вполне вероятно, что вы должны предпочесть &impl MyTrait<T>.

0 голосов
/ 13 декабря 2018

Это действительно другое.Версия impl эквивалентна следующему:

fn confirm<T, M: MyTrait<T>>(subject: M) ...

, поэтому в отличие от первой версии, subject перемещается (передается по значению) в confirm, а не передается по ссылке.Таким образом, в версии impl confirm становится владельцем этого значения.

...