5 ms·
Whether something is breaking or not can be subjective. You can change the behavior of an API in breaking ways without changing the interface. There are so ma
by jopsen 3y ago
Whether something is breaking or not can be subjective.
You can change the behavior of an API in breaking ways without changing the interface.
There are so many edge cases.
- tialaramex 3y agoSure, Hyrum's law applies. For example one of the game or 3D libraries blew up people's stuff a while back because Rust's structure layout behaviour changed. The library cared about type layout, but it neither asked its users to ensure layout (which you can do with a Rust attribute) nor checked the layout (which you could also do with a macro if you put some work in) - it just blindly assumed it knew how things are laid out, even though Rust specifically tells you that's not the default behaviour - and so it broke. 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.
- mgaunard 3y agoI'm not a Rust developer, but my understanding is that if you break semver, which is very easy to do by mistake, you're in the same situation as in C++ with ODR issues, which are undefined behaviour. For a language that touts itself as a safer C++, that's definitely bad.
- lifthrasiir 3y agoSemantic versioning of Rust crates is just a convention and doesn't affect the correctness. Compiler (rustc) in particular has no notion of crates at all and will error whenever users try to use whatever API incorrectly.
- mgaunard 3y agoIt affects what gets rebuilt.
- lifthrasiir 3y agoThat's not even true for C/C++. A patch version can still update header files and C/C++ build systems can't detect whether the update is semantic or not. In Rust the serialized crate metadata can be used to detect if the recompilation is needed, and it is completely orthogonal to the semantic versioning.
- steveklabnik 3y ago(Rustc does know what crates are. You pass a crate root to rustc. You pass —extern to point at other crates. Rustc has no notion of packages.)
- tialaramex 3y agoC++ ODR is not just "Undefined Behaviour" it's an example of IFNDR, a get-out in which the language standard just washes its hands of difficult problems by saying in that case what you've written is silently not a C++ program, so its behaviour is arbitrary but you don't get any compiler diagnostics. 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.
- mgaunard 3y agoThat only works if you're dealing with source, where it's possible to check for coherence. The problem happens when you link inconsistent binaries.
- lifthrasiir 3y agoIn which case static or dynamic linker will simply fail because Rust symbols have hashes derived from ABI attached by default (unless `#[no_mangle]` is used and you're on your own) and those hashes won't match unless the identical signature was used.
- nrabulinski 3y agoExcept it’s not really subjective. 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
- mgaunard 3y agoAPI breakage and ABI breakage are two different things.
- nrabulinski 3y agoRust has no stable ABI (simplification for the sake of discussing crates) and instead sources of all dependencies are fetched and compiled along with your program hence we’re talking about crate’s API and not a library’s ABI 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)
- mgaunard 3y agoThe build system needs to know what source files it needs to rebuild and for which ones it can just reuse previously-generated build artifacts, typically by detecting what changed in the source. 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.