Возможно ли, что изменение типа аргумента функции с 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
заменяется на общий. Если какой-либо из них может сломаться по течению, покажите, как это сделать. И если я пропустил какой-то случай, объясните, пожалуйста, если он ломается.
impl Trait
// before
pub fn foo(_: impl Trait) {}
// after
pub fn foo<T: Trait>(_: T) {}
impl Trait
, но меняются только некоторые из них// before
pub fn foo(_: impl Trait1, _: impl Trait2, _: impl Trait3) {}
// after
pub fn foo<T1: Trait1, T2: Trait2>(_: T1, _: T2, _: impl Trait3) {}
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) {}
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) {}
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) {}
Один из случаев, когда это происходит, - это когда вы хотите, чтобы функция принимала два аргумента, оба из которых реализуют некоторую особенность 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`.
Недавно я отредактировал эту часть ссылки, поскольку она устарела. Вы можете увидеть обновление в ночной версии (стабильная версия должна появиться завтра, 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 Разве ты не имел в виду примеры 4 и 5?
Этот ответ можно было бы улучшить, объяснив, почему это может быть критическим изменением, если существует хотя бы один универсальный вариант. (Я предполагаю, что общая «арность» изменится, что приведет к поломке всех вызывающих абонентов, которые явно указали дженерики.) Также было бы неплохо задокументировать, какие из пронумерованных изменений OP нарушаются. (Я предполагаю № 4 и № 5 по той же причине.)
Это хороший улов. Я добавлю отредактировать свой вопрос, чтобы исключить такую возможность.