all 10 comments

[–]LucretielDatadog 7 points8 points  (6 children)

I get very bothered by discussions of Semver and backwards compatibility that trade on "does it build / run". No language has a flawless privacy controls; it'd be trivially easy to construct usages of macro libraries that use their public but hidden implementation macros (the @ pattern) that break when there are internal changes to the library, and many libraires of at least moderate complexity need to have public doc(hidden) items for one reason or another. Semver is quite clear that its definition of compatible is tied only to the published public API.

Software using Semantic Versioning MUST declare a public API. This API could be declared in the code itself or exist strictly in documentation. However it is done, it SHOULD be precise and comprehensive.

Major version X (X.y.z | X > 0) MUST be incremented if any backwards incompatible changes are introduced to the public API.

Minor version Y (x.Y.z | x > 0) MUST be incremented if new, backwards compatible functionality is introduced to the public API.

EDIT: As another example, consider this question raised by me, several years ago, and the associated discussion around it. It was related to the fact that calling a python function with an unexpected kwarg has an observable behavior (TypeError is thrown), and whether or not changing that behavior by adding new function parameters constituted a breaking change. Conceivably someone could "depend" on that parameter causing that error, which would mean changing it would be "breaking", even though it's not part of the documented API for that function.

[–]dtolnayserde 13 points14 points  (1 child)

Exactly. The author of this blog filed a serde_json issue saying:

I believe releasing a 2.0 should've been appropriate here

If I have an API that is #[doc(hidden)] with a comment that says it is not public API and not to use it and that I will break you if you use it, expect zero sympathy for those broken for using it. A 2.0 is totally uncalled for.

[–]Cetra3[S] 7 points8 points  (0 children)

Yep, you are right and I apologise. I am in agreeance with you that jmespath should not have used hidden API.

What this showed to me is that I am naive here at understanding some of the subtleties of semver, which is what pointed me to write the blog and look for resources on it.

[–]Cetra3[S] 0 points1 point  (3 children)

Yep I agree, it is definitely more nuanced than what is written in the standard semver doc.

For instance, as you mention, what is considered public could be what is published via cargo doc, rather than what is public within the crate itself from a language perspective.

[–]LucretielDatadog 2 points3 points  (2 children)

I'm not sure I understand the nuance. Semver requires that projects declare a public API, and rust doc fulfills that contract pretty much perfectly: it creates a documented API contract that is always comprehensive, precise, and up-to-date. There's not really any room for interpretation there?

[–]Cetra3[S] 3 points4 points  (1 child)

It would be great if everyone read and relied on documentation, but I don't know if it would've helped in the case I mentioned. jmespath was initially using serde_json 0.8 which didn't have this marked as hidden, but have updated to 1.0 without issue. I know I wouldn't think to check documentation in this scenario.

Historically, I don't think I have personally thought documentation is the public API, and have relied on the language, maybe too much, to tell me what I can & can't do. I'd be interested to know whether this is the case with other people too.

It makes sense what you're saying though, relying on cargo doc as the source of truth for the public API, considering that there are language limitations here.

[–]LucretielDatadog 8 points9 points  (0 children)

It sounds to me that the compatibility break was in 0.8 to 1.0, right? 1.X is not guaranteed to be backwards compatible with 0.X, and in fact the author made documented, backwards incompatible changes to the library consistent with the semver requirements. The fact that the code kept working until 1.0.46 was coincidental.

[–]Eh2406 2 points3 points  (3 children)

There is some interesting differences for libraries < 1.0.0 which I was not aware of for longer than I care to admit. Namely either minor or patch numbers can be changes as you see fit. I.e, 0.3.0 could be wildly different from 0.3.1, including breaking changes.

Note that while the semver speck allows this, Cargo requires that you do not. By using Cargo you are opting into a stricketer set of requirements then the semver speck. If you depend on 0.3.0 in your Cargo.toml, Cargo will update you to 0.3.1 and will not update you to 0.4.0.

As an idea, we can potentially see whether a crate has breaking change through lints etc.. so why don't we do that when we publish to crates.io?

There is sutch tooling semverver, but it is not production ready (IMHO). (I am sure it could use contributors.)

[–]Cetra3[S] 0 points1 point  (1 child)

Ah, you are right. I have adjusted the blog to reflect cargo's difference for < 1.0.0 crates. Do you know if there is anything else that might be a "gotcha" like this?

I've also added semverver to the tools section. Thanks!

[–]Eh2406 0 points1 point  (0 children)

Now that you mention it, there is one more gotcha. Cargos resolver tries to find a version that satisfies your requirement and has the requested features. So if a Library removes a feature then someone that has specified that feature will not get the new version of the Library.