Impl trait
Syntax
ImplTraitType → impl TypeParamBounds
ImplTraitTypeOneBound → impl TraitBound
impl Trait предоставляет способы указания неназванных, но конкретных типов, которые
реализуют определенный трейт.
Он может появляться в двух видах мест: позиция аргумента (где он может действовать как анонимный параметр типа для функций) и позиция возврата (где он может действовать как абстрактный тип возврата).
#![allow(unused)] fn main() { trait Trait {} impl Trait for () {} // позиция аргумента: анонимный параметр типа fn foo(arg: impl Trait) { } // позиция возврата: абстрактный тип возврата fn bar() -> impl Trait { } }
Анонимные параметры типа
Note
Это часто называют “impl Trait в позиции аргумента”. (Термин “параметр” здесь более корректен, но “impl Trait в позиции аргумента” — это формулировка, использовавшаяся при разработке этой функции, и она остается в частях реализации.)
Функции могут использовать impl, за которым следует набор ограничений трейтов, чтобы объявить параметр как имеющий анонимный тип.
Вызывающая сторона должна предоставить тип, удовлетворяющий ограничениям, объявленным анонимным параметром типа, и функция может использовать только методы, доступные через ограничения трейтов анонимного параметра типа.
Например, эти две формы почти эквивалентны:
#![allow(unused)] fn main() { trait Trait {} // обобщенный параметр типа fn with_generic_type<T: Trait>(arg: T) { } // impl Trait в позиции аргумента fn with_impl_trait(arg: impl Trait) { } }
То есть impl Trait в позиции аргумента — это синтаксический сахар для обобщенного параметра типа, такого как <T: Trait>, за исключением того, что тип анонимен и не появляется в списке GenericParams.
Note
Для параметров функции обобщенные параметры типа и
impl Traitне совсем эквивалентны. С обобщенным параметром, таким как<T: Trait>, вызывающая сторона имеет возможность явно указать обобщенный аргумент дляTна месте вызова с помощью GenericArgs, например,foo::<usize>(1). Изменение параметра с одного на другой может представлять собой критическое изменение для вызывающих функцию, поскольку это изменяет количество обобщенных аргументов.
Абстрактные типы возврата
Note
Это часто называют “impl Trait в позиции возврата”.
Функции могут использовать impl Trait для возврата абстрактного типа возврата.
Эти типы заменяют другой конкретный тип, где вызывающая сторона может использовать только методы, объявленные указанным Trait.
Каждое возможное возвращаемое значение из функции должно разрешаться в один и тот же конкретный тип.
impl Trait в позиции возврата позволяет функции возвращать неупакованный абстрактный тип.
Это особенно полезно с замыканиями и итераторами.
Например, замыкания имеют уникальный, не записываемый тип.
Ранее единственным способом вернуть замыкание из функции было использование трейт-объекта:
#![allow(unused)] fn main() { fn returns_closure() -> Box<dyn Fn(i32) -> i32> { Box::new(|x| x + 1) } }
Это могло повлечь штрафы производительности из-за выделения в куче и динамической диспетчеризации.
Было невозможно полностью указать тип замыкания, только использовать трейт Fn.
Это означает, что трейт-объект необходим.
Однако с impl Trait можно написать это проще:
#![allow(unused)] fn main() { fn returns_closure() -> impl Fn(i32) -> i32 { |x| x + 1 } }
что также избегает недостатков использования упакованного трейт-объекта.
Аналогично, конкретные типы итераторов могут стать очень сложными, включая типы всех предыдущих итераторов в цепочке.
Возврат impl Iterator означает, что функция раскрывает только трейт Iterator как ограничение на свой тип возврата, вместо явного указания всех других задействованных типов итераторов.
impl Trait в позиции возврата в трейтах и реализациях трейтов
Функции в трейтах также могут использовать impl Trait как синтаксис для анонимного ассоциированного типа.
Каждый impl Trait в типе возврата ассоциированной функции в трейте десугарируется в анонимный ассоциированный тип. Тип возврата, который появляется в сигнатуре функции реализации, используется для определения значения ассоциированного типа.
Захват
За каждым абстрактным типом impl Trait в позиции возврата стоит некоторый скрытый конкретный тип. Чтобы этот конкретный тип использовал обобщенный параметр, этот обобщенный параметр должен быть захвачен абстрактным типом.
Автоматический захват
Абстрактные типы impl Trait в позиции возврата автоматически захватывают все обобщенные параметры в области видимости, включая обобщенные параметры типа, константы и времени жизни (включая вышеранговые).
2024 Edition differences
До редакции 2024, в свободных функциях и в ассоциированных функциях и методах собственных реализаций, обобщенные параметры времени жизни, которые не появляются в ограничениях абстрактного типа возврата, не захватываются автоматически.
Точный захват
Набор обобщенных параметров, захватываемых абстрактным типом impl Trait в позиции возврата, может быть явно контролируем с помощью ограничения use<..>. Если присутствует, только обобщенные параметры, перечисленные в ограничении use<..>, будут захвачены. Например:
#![allow(unused)] fn main() { fn capture<'a, 'b, T>(x: &'a (), y: T) -> impl Sized + use<'a, T> { // ~~~~~~~~~~~~~~~~~~~~~~~ // Захватывает только `'a` и `T`. (x, y) } }
В настоящее время только одно ограничение use<..> может присутствовать в списке ограничений, все обобщенные параметры типа и константы в области видимости должны быть включены, и все параметры времени жизни, которые появляются в других ограничениях абстрактного типа, должны быть включены.
В пределах ограничения use<..> любые присутствующие параметры времени жизни должны появляться перед всеми обобщенными параметрами типа и константы, и опущенное время жизни ('_) может присутствовать, если ему разрешено появляться в типе возврата impl Trait.
Поскольку все обобщенные параметры типа в области видимости должны быть включены по имени, ограничение use<..> не может использоваться в сигнатуре элементов, которые используют impl Trait в позиции аргумента, так как эти элементы имеют анонимные параметры типа в области видимости.
Любое ограничение use<..>, которое присутствует в ассоциированной функции в определении трейта, должно включать все обобщенные параметры трейта, включая неявный обобщенный параметр типа Self трейта.
Различия между обобщениями и impl Trait в позиции возврата
В позиции аргумента impl Trait очень похож по семантике на обобщенный параметр типа.
Однако есть значительные различия между ними в позиции возврата.
С impl Trait, в отличие от обобщенного параметра типа, функция выбирает тип возврата, и вызывающая сторона не может выбрать тип возврата.
Функция:
#![allow(unused)] fn main() { trait Trait {} fn foo<T: Trait>() -> T { // ... panic!() } }
позволяет вызывающей стороне определить тип возврата, T, и функция возвращает этот тип.
Функция:
#![allow(unused)] fn main() { trait Trait {} impl Trait for () {} fn foo() -> impl Trait { // ... } }
не позволяет вызывающей стороне определить тип возврата.
Вместо этого функция выбирает тип возврата, но только обещает, что он будет реализовывать Trait.
Ограничения
impl Trait может появляться только как параметр или тип возврата не-extern функции.
Он не может быть типом привязки let, типом поля или появляться внутри псевдонима типа.