Unison is an open source functional programming language based on a simple idea with big implications: code is content-addressed and immutable.
Homepage: https://www.unisonweb.org/
Strange Loop talk: https://www.youtube.com/watch?v=gCWtkvDQ2ZI
My understanding (and the way I've versioned my projects) is that a major version denotes a stable API and behavior you can safely rely on. What functions are exported, what arguments they take, etc. That's a pretty easy thing to not break.
Things that _aren't_ covered are implementation details such as the location of files in the package, the reuse of objects internally, or other not explicitly-defined behavior.
A couple of examples:
1. In JS, you can import from sub-folders: `let const = require('somePkg/a/b/c)`, but that depends on internal organization. That's not guaranteed across versions.
2. You can also add properties to an object. If we export a function that takes a request object and returns a response, we don't guarantee those are identical objects. We _do_ ensure that the object returned from the function has properties x,y,z, but not that anything you defined before calling the function will still be there.
We would have this back-and-forth on how to version fix releases. "Well, if someone was depending on this buggy behavior, this update will break their code". Ultimately, we said "this is what we guarantee will work. For anything else, run tests anytime you upgrade".
We're not perfect (all software has bugs), but this has worked pretty well.
The answer IMO is to just do a full test of your stuff after doing updates. I update many packages at once to make the most of my time.
My point is not that this is an unsolvable problem, but that it’s not a good idea to go “semver says this is non breaking, let’s just chuck it in production”
Necessity, meet invention...
I hope that eventually it will be possible to confirm API compatibility (however it is defined) as part of making a release. Whether that can be done on the crates.io server or not is another matter, because it could vary by platform, building could require local dependencies, &c. This isn’t full tests, of course, just API, so there’d still be scope for error.
- Defining semver requirements for the ecosystem: https://stackoverflow.com/questions/41185023/what-exactly-is...
- There's also a library that attempts to automatically check sermver adherence of a crate: https://github.com/rust-lang/rust-semverver
- And there has been quite a bit of effort into preventing semver requirements from fracturing the ecosystem. This revolves around the compiler working with multiple major versions of a single library: https://github.com/dtolnay/semver-trick
Being able to do that depends on whether I can assign such things after the fact, or if the author has to do it.
Using tests across versions is a definitely a trick to consider but your coverage may vary. Probably better to do it in addition to other methods.
As for code coverage, that is always a problem, and not one to sneeze at.
So follow me here. I do a lot of tech selection for teams. Not just which libraries have he features we need, which ones are going to result in. One pair programming due to human error.
If I see that the poorly documented (accidental?) feature I care about has poor code coverage, should I use it? Should I use it and file a PR with pinning tests?
TLDR, it doesn't change much, what means it doesn't create a lot of problems, but doesn't really solve any either. Versioning is a humane concept, and you can't solve it with pure tech, you'll always need guidelines and procedures.
We've flirted with separating interface from implementation over and over again. We also talk sometimes about the tests being the actual requirements definition for the system.
If we versioned the tests and declared which version of the tests pass with a given version of the code, we might get closer to a workable system. Most of the time a bug fix should result in new tests, not changing old tests - unless there are backward incompatible changes. This may require us to up our test writing game, but that sounds like a good carrot to me.
Semver is mostly about 'increment a number on every change, increment another number when the changes are not backward compatible (covariant?)' for some definition of compatible. But if we have tried to define 'compatible' in a rigorous way, I haven't seen it. I suspect someone in Formal Methods has something up their sleeve. I'd be curious to know if they have anything we can cherry-pick.