Может ли изменение impl Trait на generic в аргументе функции стать критическим изменением?

Возможно ли, что изменение типа аргумента функции с impl Trait на общий является критическим изменением? В справочнике по Rust говорится, что:

Примечание. Для параметров функции параметры универсального типа и impl Trait не совсем эквивалентны. С помощью универсального параметра, такого как <T: Trait>, вызывающая сторона имеет возможность явно указать общий аргумент для T на месте вызова с помощью GenericArgs, например foo::<usize>(1). Если impl Trait является типом любого параметра функции, то вызывающая сторона никогда не сможет предоставить какие-либо общие аргументы при вызове этой функции. Сюда входят универсальные аргументы для возвращаемого типа или любые константные дженерики.

Следовательно, изменение сигнатуры функции с одной на другую может стать критическим изменением для вызывающих функцию.

Но учитывая, что если есть хотя бы один параметр impl Trait, то вызывающая сторона не может называть какие-либо общие аргументы, не должно ли это означать, что изменение impl Trait на <T: Trait> может позволить вызывающей стороне только называть общие аргументы? Поскольку дженерики уже были выведены, мы также не можем нарушить выведение типов.

Обновлено: Я предполагаю, что каждый impl Trait изменяется на другой и уникальный общий тип (без совпадения с другими impl Trait или предыдущими дженериками). Нарушение этого правила, очевидно, будет нарушением, спасибо @啊鹿Dizzyi за указание на это.

Я перечисляю здесь список (по моему мнению, исчерпывающий) случаев, когда impl Trait заменяется на общий. Если какой-либо из них может сломаться по течению, покажите, как это сделать. И если я пропустил какой-то случай, объясните, пожалуйста, если он ломается.

  1. Только меняем impl Trait
// before
pub fn foo(_: impl Trait) {}

// after
pub fn foo<T: Trait>(_: T) {}
  1. Много impl Trait, но меняются только некоторые из них
// before
pub fn foo(_: impl Trait1, _: impl Trait2, _: impl Trait3) {}

// after
pub fn foo<T1: Trait1, T2: Trait2>(_: T1, _: T2, _: impl Trait3) {}
  1. Много impl Trait, все меняю
// before
pub fn foo(_: impl Trait1, _: impl Trait2, _: impl Trait3) {}

// after
pub fn foo<T1: Trait1, T2: Trait2, T3: Trait3>(_: T1, _: T2, _: T3) {}
  1. Некоторые общие типы, изменены некоторые impl Trait, но оставлен хотя бы один
// before
pub fn foo<T1: Trait1>(_: T1, _: impl Trait2, _: impl Trait3) {}

// after
pub fn foo<T1: Trait1, T2: Trait2>(_: T1, _: T2, _: impl Trait3) {}
  1. Некоторые общие типы, меняющие все impl Trait.
// before
pub fn foo<T1: Trait1>(_: T1, _: impl Trait2, _: impl Trait3) {}

// after
pub fn foo<T1: Trait1, T2: Trait2, T3: Trait3>(_: T1, _: T2, _: T3) {}
Почему Python в конце концов умрет
Почему Python в конце концов умрет
Последние 20 лет были действительно хорошими для Python. Он прошел путь от "просто языка сценариев" до основного языка, используемого для написания...
1
0
64
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Один из случаев, когда это происходит, - это когда вы хотите, чтобы функция принимала два аргумента, оба из которых реализуют некоторую особенность Trait, но вас не волнует, являются ли два аргумента одного и того же типа или нет.

Поскольку, когда вы определяете общий тип T, все виды привязываются к этому же типу. Это приведет к ошибке, поэтому вам нужно быть осторожным при изменении.

Пример

trait Trait {}

fn foo_impl(_: impl Trait, _: impl Trait) {}
fn foo_generic<T: Trait>(_: T, _: T) {}
fn foo_generic_ok<T1: Trait, T2: Trait>(_: T1, _: T2) {}

impl Trait for i32 {}
impl Trait for f32 {}

fn main() {
    foo_impl(1, 1.0);
    foo_generic(1, 1.0); // <-- this will error
    foo_generic_ok(1, 1.0);
}

Сообщение об ошибке

error[E0308]: mismatched types
  --> ****\src/main.rs:12:20
   |
12 |     foo_generic(1, 1.0);
   |     ----------- -  ^^^ expected `i32`, found floating-point number
   |     |           |
   |     |           expected all arguments to be this `i32` type because they need to match the type of this parameter
   |     arguments to this function are incorrect
   |
note: function defined here
  --> ****\src/main.rs:4:4
   |
4  | fn foo_generic<T: Trait>(_: T, _: T) {}
   |    ^^^^^^^^^^^ -         ----  ---- this parameter needs to match the `i32` type of {unknown}
   |                |         |
   |                |         {unknown} needs to match the `i32` type of this parameter
   |                {unknown} and {unknown} all reference this parameter T

For more information about this error, try `rustc --explain E0308`.

Это хороший улов. Я добавлю отредактировать свой вопрос, чтобы исключить такую ​​возможность.

Aleksander Krauze 04.09.2024 10:10
Ответ принят как подходящий

Недавно я отредактировал эту часть ссылки, поскольку она устарела. Вы можете увидеть обновление в ночной версии (стабильная версия должна появиться завтра, 5 сентября).

Короче говоря, в версии 1.63.0 добавлена ​​возможность указывать дженерики, когда присутствует impl Trait. Насколько я могу судить, вы правы в том, что это не может быть кардинальным изменением. Начиная с версии 1.63.0, это изменение может стать критическим, если существует хотя бы один универсальный вариант.

Это означает, что ваши примеры 4 и 5 являются критическими изменениями.

use std::fmt::Display;

// before
pub fn foo1<T1: Display>(_: T1, _: impl Display, _: impl Display) {}

// after
pub fn foo2<T1: Display, T2: Display>(_: T1, _: T2, _: impl Display) {}

fn main() {
    foo1::<i32>(1, 2, 3);
    foo2::<i32>(1, 2, 3);
}

error[E0107]: function takes 2 generic arguments but 1 generic argument was supplied
  --> src/main.rs:11:5
   |
11 |     foo2::<i32>(1, 2, 3);
   |     ^^^^   --- supplied 1 generic argument
   |     |
   |     expected 2 generic arguments
   |
note: function defined here, with 2 generic parameters: `T1`, `T2`
  --> src/main.rs:7:8
   |
7  | pub fn foo2<T1: Display, T2: Display>(_: T1, _: T2, _: impl Display) {}
   |        ^^^^ --           --
help: add missing generic argument
   |
11 |     foo2::<i32, _>(1, 2, 3);
   |               +++

For more information about this error, try `rustc --explain E0107`.

Полная версия в ПР №1495.

TLDR PR: примеры 3 и 4 являются критическими изменениями, потому что если кто-то вызвал функцию с явными дженериками (например, foo<u32>), то ему нужно добавить заполнитель _, чтобы вызвать вывод для добавленных дженериков (т. е. foo<u32, _>).

Jmb 04.09.2024 10:50

Это именно то, что я хотел узнать, спасибо. И спасибо за поддержание документации в актуальном состоянии. :)

Aleksander Krauze 04.09.2024 10:55

@Jmb Разве ты не имел в виду примеры 4 и 5?

Aleksander Krauze 04.09.2024 10:56

Этот ответ можно было бы улучшить, объяснив, почему это может быть критическим изменением, если существует хотя бы один универсальный вариант. (Я предполагаю, что общая «арность» изменится, что приведет к поломке всех вызывающих абонентов, которые явно указали дженерики.) Также было бы неплохо задокументировать, какие из пронумерованных изменений OP нарушаются. (Я предполагаю № 4 и № 5 по той же причине.)

user4815162342 04.09.2024 11:27

Другие вопросы по теме