Closure type change and semver

If I have a method that consumes self and takes in a closure:

impl Something {
  pub fn finalize(self, f: F)
  where
    F: Fn(&Self)
  {
    // do stuff
    f(&self);
    // do more stuff
  }
}

That Fn should be FnOnce. (There are also a case of an FnMut that should be FnOnce as well, where the situation is near identical).

The bad closure types are old copy'n'paste errors -- they were always meant to be FnOnce. Assuming no one looked at the code an thought "Neat, I can allocate a single closure and pass it to multiple finalize() calls", are there any situations where simply converting these to FnOnce without bumping breaking change semver version would cause a problem?

How dragged would one be if one just fixed those {Fn, FnMut} → FnOnce, yanked the old crate (to explicitly denote that it wasn't really a breaking change, it was rather an error in the old crate)?

(For Reasons(tm) I really want to avoid bumping the version number beyond the minor version at this point in time).

Because FnOnce is a supertrait of Fn, this change is explicitly more permissive so should be non-breaking in all cases. The only exception would be some kind of inference failure arising from the extra options available to the trait solver, but off the top of my head I can't think of how that would happen here.

3 Likes

Cargo says loosening a generic parameter (which we'd be doing given that FnOnce is a supertrait of both Fn and FnMut) is a minor change. I wouldn't go as far as yanking the old version though, a slightly too strict trait bound is not a sufficient reason to yank a version.

3 Likes