Not only will people break it, it's inconvenient when doing active development, tweaking things here and there.
Just autodetect whatever you need to in the first place.
Not only will people break it, it's inconvenient when doing active development, tweaking things here and there.
Just autodetect whatever you need to in the first place.
You can change the behavior of an API in breaking ways without changing the interface.
There are so many edge cases.
This tool is a linter, with I believe no false positives. So if it says you've broken semver constraints, you can decide "Oh, whoops, I knew there's a reason I didn't change that, I'll put it back" or "Time for a new version" or, I guess, YOLO although in that case I wonder why you bother running the tool.
For a language that touts itself as a safer C++, that's definitely bad.
Rust doesn't have this get-out. To some extent that's because it's not a formal standard, but mostly it's because IFNDR is diametrically opposed to correctness. Many C++ programs (perhaps even the vast majority) technically have no meaning whatsoever due to IFNDR which is bad -- it's not that the C++ compiler judges them to be correct when they aren't, but that it has no opinion either way, ever and programmers mistake "it compiled" for success.
Thus, in Rust you don't get "ODR errors" here, your program won't compile.
Here's a nice easy Rust semver break example: I make a library about US vacation booking, it has a public enumerated type FederalHoliday but initially that enum doesn't mention Juneteenth - whoops. Somebody points that out to me, and I correct the omission, but I forgot it's in a public enum. So that's a semver break.
Why? If somebody writes code using FederalHoliday from my library, they're entitled to exhaustively match against the enumerated type. So they can make code to handle MLK Day and Thanksgiving and so on, and of course they had no reason to handle Juneteenth because it wasn't in the list - then one day they take my update. Their program no longer builds, it complains that their FederalHoliday code isn't handling Juneteenth, I made a semver breaking change.
Now, in this type of case the correct fix (which you need to do before release, as introducing it later is a semver break) is to mark the enumeration as #[non_exhaustive] which tells Rust I know what's in this enumeration, so I can use it as an exhaustive match in my own code, but outside this crate, you can't match it exhaustively, you need to write a default case to handle new additions.
The problem happens when you link inconsistent binaries.
You have a type T, and a struct U which contains a T.
A function takes a U by reference, and therefore will be mangled with U.
However say that function was built with a different version of T than the caller. This doesn't affect mangling. Things silently work even though they break.
This is a basic pitfall of working with any system that allows linking of binaries.
The setup only works so long as you guarantee that all entities with the same name are indeed the same thing. This is the One Definition Rule.
How does this differ from undefined behavior?
Because it is runtime behaviour UB is constrained, if the only UB is in your "Delete files" code, and that code only executes when the user hits the "Delete" button, we can say that this UB won't occur when the user doesn't hit the button.
But IFNDR isn't like that, since the entire program has no meaning, nothing about it is required at all. Maybe the compiler output is 40GB of zeroes. Maybe when you run the program it immediately segfaults. Maybe it works exactly as you expected... except it's a bit... off and you can't exactly say why.
Given that IFNDR is a static, rather than a dynamic property, why can't compilers detect it and error? Are all IFNDR issues similar to the ODR one you mentioned in that they only arise at link time and fixing them would require changing how symbols are mangled?
No, they're worse. Are you comfortable with basic CS theory? If not this is likely to whizz over your head, sorry.
A typical IFNDR issue depends upon the semantic properties of the alleged C++ program. Rice's Theorem tells us that all non-trivial semantic properties are Undecidable. No algorithm can exist to determine whether a program has these properties except if trivially all the programs in your language have the property (e.g. imagine a toy language with no jumping or iterating features, it must halt) or none do (e.g. imagine a resolutely single threaded language, it doesn't have any data races).
Because it's Undecidable, having the compiler decide if your alleged C++ program has the desired properties and if not report an error is intractable. So the C++ standard just says if it doesn't have that property it's actually not a C++ program, too bad, the compiler shouldn't worry about this case at all, just press on.
Now, the good news about Rice's Theorem is that we do have another option. When we're not sure if the semantic property we wanted is present, we reject the program, with a diagnostic informing the programmer that we couldn't prove it had this desirable property.
I'd argue this approach is much better, but it does mean you can write programs in Rust (which has this behaviour) where even though you are totally correct that the program would actually be OK the compiler rejects your program because it can't see why you're right.
Does it make the code which previously compiled not compile?
Yes - Breaking
No - then, does it make the code which previously worked as expected do something unexpected?
Yes - Breaking
No - Not breaking
I agree that the second part is tricky as you need to cover all usecases that used to be considered valid, but it’s not impossible, especially with a well defined API
And yes, I’d say that if a program did not error at runtime but after upgrading your library it started to - that’s still a breaking change (even worse than one that you can detect at build time)
What changed in the source outside of your project is driven by whether the semver changed. Semver not changing means that the source didn't change and you can link existing binaries as-is.
Then there is the whole business of dynamic linking.
It's a workflow problem, things are done the wrong way around.
Which is exactly what cargo-semver-checks does.
Like, the first line of the README after the name is "Lint your crate API changes for semver violations." - it quite specifically works with the actual crate API, not commit names, comments or any other human annotation.
It also will show you what broke semver and how, so you can fix it easily. And as the author stated, the goal is to suggest the correct version in the future.
Verifying that a semver change is correct is not the same as automatically generating the semver.
It's literally P vs NP.