Синтаксис &impl Trait

impl в этом контексте создает синтаксическое сокращение для ограничения типажа (trait bound).

1. Без impl — что было бы?

Если бы не было impl, синтаксис стал бы неоднозначным:

#![allow(unused)]
fn main() {
// Так НЕ работает в Rust!
pub fn notify(item: &Summary) {  // ❌ Не компилируется!
    println!("Breaking news! {}", item.summarize());
}
}

Почему это проблема? Потому что Summary — это типаж (trait), а не тип. В Rust нельзя иметь переменные или параметры типа "просто типаж" — это намеренное дизайнерское решение.

2. Альтернатива — явные ограничения типажей

Без impl синтаксис был бы более многословным:

#![allow(unused)]
fn main() {
// Явное ограничение типажа (trait bound)
pub fn notify<T: Summary>(item: &T) {
    println!("Breaking news! {}", item.summarize());
}
}

3. Преимущества impl синтаксиса

✅ Лаконичность для простых случаев

#![allow(unused)]
fn main() {
// Коротко и понятно
pub fn notify(item: &impl Summary) { ... }

// Против более многословного
pub fn notify<T: Summary>(item: &T) { ... }
}

✅ Удобство для нескольких параметров

#![allow(unused)]
fn main() {
// С impl - могут быть разные типы, реализующие Summary
pub fn notify(item1: &impl Summary, item2: &impl Summary) { ... }

// С generic - тот же тип T для обоих параметров
pub fn notify<T: Summary>(item1: &T, item2: &T) { ... }

// Если нужны разные типы с generic - многословно
pub fn notify<T: Summary, U: Summary>(item1: &T, item2: &U) { ... }
}

4. Когда использовать impl, а когда явные generic

Используйте impl:

#![allow(unused)]
fn main() {
// Простые случаи с одним параметром
pub fn display(item: &impl Display) { ... }

// Когда параметры могут быть разных типов
pub fn compare(a: &impl Display, b: &impl Display) { ... }
}

Используйте явные generic:

#![allow(unused)]
fn main() {
// Когда нужна связь между параметрами
pub fn same_type(a: &T, b: &T) -> bool { ... }

// Когда тип появляется в возвращаемом значении
pub fn factory<T: Default>() -> T { ... }

// Для сложных ограничений типажей
pub fn complex<T: Summary + Display + Clone>(item: &T) { ... }

// Для where clauses
pub fn advanced<T>(item: &T) where T: Summary + Serialize { ... }
}

5. Что на самом деле происходит

Оба варианта эквивалентны с точки зрения компилятора:

#![allow(unused)]
fn main() {
// Эти две функции мономорфизируются одинаково
pub fn notify_impl(item: &impl Summary) { ... }
pub fn notify_generic<T: Summary>(item: &T) { ... }
}

Компилятор создает специализированные версии функций для каждого конкретного типа, который передается в функцию.

Итог

impl в параметрах функций — это синтаксический сахар, который:

  • Делает код более читаемым для простых случаев
  • Избегает введения лишних generic-параметров
  • Сохраняет гибкость типизации
  • Остается полностью типобезопасным