Search code examples
rustsemantic-versioning

Can changing impl Trait to generic in function argument be a breaking change?


Is it possible that changing function argument type from impl Trait to a generic is a breaking change? The Rust Reference states that:

Note: For function parameters, generic type parameters and impl Trait are not exactly equivalent. With a generic parameter such as <T: Trait>, the caller has the option to explicitly specify the generic argument for T at the call site using GenericArgs, for example, foo::<usize>(1). If impl Trait is the type of any function parameter, then the caller can't ever provide any generic arguments when calling that function. This includes generic arguments for the return type or any const generics.

Therefore, changing the function signature from either one to the other can constitute a breaking change for the callers of a function.

But given that, if there is at least one impl Trait parameter, then caller cannot name any generic arguments, shouldn't that imply that changing impl Trait to <T: Trait> can only enable callers to name generics arguments? Since generics were already inferred, we cannot also break type inference.

EDIT: I assume that each impl Trait is changed to a different and unique generic type (no overlap with other impl Traits or previous generics). Violating this would obviously be breaking, thanks to @啊鹿Dizzyi for pointing that.

I list here a list of (what I believe is an exhaustive) cases where impl Trait is changed to a generic. If any one of them could break downstream, please show how. And if I missed some case, please explain if it is breaking.

  1. Changing only impl Trait
// before
pub fn foo(_: impl Trait) {}

// after
pub fn foo<T: Trait>(_: T) {}
  1. Many impl Trait, but only changing some of them
// before
pub fn foo(_: impl Trait1, _: impl Trait2, _: impl Trait3) {}

// after
pub fn foo<T1: Trait1, T2: Trait2>(_: T1, _: T2, _: impl Trait3) {}
  1. Many impl Trait, changing all of them
// before
pub fn foo(_: impl Trait1, _: impl Trait2, _: impl Trait3) {}

// after
pub fn foo<T1: Trait1, T2: Trait2, T3: Trait3>(_: T1, _: T2, _: T3) {}
  1. Some generic types, changing some impl Traits, but leaving at least one
// before
pub fn foo<T1: Trait1>(_: T1, _: impl Trait2, _: impl Trait3) {}

// after
pub fn foo<T1: Trait1, T2: Trait2>(_: T1, _: T2, _: impl Trait3) {}
  1. Some generic types, changing all of impl Traits
// before
pub fn foo<T1: Trait1>(_: T1, _: impl Trait2, _: impl Trait3) {}

// after
pub fn foo<T1: Trait1, T2: Trait2, T3: Trait3>(_: T1, _: T2, _: T3) {}

Solution

  • I recently edited this part of the reference as it was outdated. You can see the update in the nightly version (it should hit stable tomorrow, September 5th).

    In short, 1.63.0 added the possibility to specify generics when impl Trait is present. As far as I can tell, you're right that it couldn't be a breaking change. Since 1.63.0, it can be a breaking change if there is at least one existing generic.

    This means your examples 4 and 5 are breaking changes.

    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`.
    

    The long version is in PR #1495.