You are using an outdated browser. Please upgrade your browser to improve your experience.

All Episodes

Predrag Gruevski and Chris Krycho joined the show to talk about SemVer. We explore the challenges and the advantages of semantic versioning (aka SemVer), the need for improving the tooling around SemVer, where semantic versioning really shines and where it’s needed, Types and SemVer, whether or not there’s a better way, and why it’s not as simple as just opting out.

Featuring

Sponsors

Socket – Secure your supply chain and ship with confidence. Install the GitHub app, book a demo or learn more

RetoolThe low-code platform for developers to build internal tools — Some of the best teams out there trust Retool…Brex, Coinbase, Plaid, Doordash, LegalGenius, Amazon, Allbirds, Peloton, and so many more – the developers at these teams trust Retool as the platform to build their internal tools. Try it free at retool.com/changelog

ExpressVPN – Stop handing over your personal data to ISPs and other tech giants who mine your activity and sell off your information. Protect yourself with ExpressVPN. Go to ExpressVPN.com/changelog and get three (3) extra months free.

Fly.ioThe home of Changelog.com — Deploy your apps and databases close to your users. In minutes you can run your Ruby, Go, Node, Deno, Python, or Elixir app (and databases!) all over the world. No ops required. Learn more at fly.io/changelog and check out the speedrun in their docs.

Notes & Links

📝 Edit Notes

Chapters

1 00:00 Welcome to The Changelog 01:16
2 01:16 Sponsor: Socket 03:41
3 04:58 Let's talk SemVer 01:29
4 06:27 Why are we here? 03:28
5 09:55 SemVer in simple terms 03:48
6 13:43 Is SemVer misunderstood? 03:03
7 16:47 Hyrum's Law and breaking changes 05:17
8 22:03 Sponsor: Retool 04:30
9 26:33 Are we just polishing a turd? 15:29
10 42:02 Marketting big releases 09:16
11 51:18 Incremental adoption 04:01
12 55:19 CalVer and names like Ubuntu 07:55
13 1:03:14 Sponsor: ExpressVPN 01:50
14 1:05:04 Ubuntu, LTS, and CalVer 09:33
15 1:14:37 Concerns for backward compatability 07:41
16 1:22:19 Where can we go from here? 05:13
17 1:27:32 SemVer Nerds! Join today. 01:12
18 1:28:43 Closing thoughts 02:31
19 1:31:14 ++ Teaser 00:53

Transcript

📝 Edit Transcript

Changelog

Play the audio to listen along while you enjoy the transcript. 🎧

Well, we’re here with two awesome people that reached out via Twitter to talk about something that goes back to our roots, Jerod. The very first episode of this podcast was SemVer, technically. Remember that?

Was it 0.0.1?

0.0.1. And then we got [unintelligible 00:05:28.01] because 0.0.2 was not SemVer, but we did it anyways. So we’re here with Predrag Gruevski and Chris Krycho. Welcome to the Changelog.

Thank you.

What do you think about the fact that we semvered, or tried to, our podcast episode numbers?

Pretty sweet, right?

We tried… For like 30-ish episodes, I want to say. Like around 30 or 40, we decided “Nah, it’s not gonna work anymore.”

But when were the breaking changes?

That’s the problem, is it just wasn’t as semantic as it was intended.

Every time you change format, it’s a breaking change, right?

If we apply Rust rules, then 0.0.x, every release is a breaking change. Every one is major. So that sounds good to me.

There you go.

Technically, before 1.0 you can do whatever you want, and the versions are allowed to do whatever they say, per the SemVer spec. So…

Except in Rust.

Yes. And Npm, actually. They both have opinions. Hey, we just walked right into what we’re here to talk about.

That’s right. So we are here – if you haven’t read the title, which I’m sure will have SemVer in there somewhere, we are here to talk about semantic versioning with two SemVer nerds - I’ll just call you all that, you can take offense all you want; not meant to be offended - who have thought deeply on this topic. Adam and I, of course, have thought shallowly, I would say… No offense, Adam.

Very shallow, yeah.

Yeah, shallowly on the topic. We know it, we’ve lived with it, we’ve seen its failures and its successes… But let’s get to know you by way of SemVer. So Predrag, you have been working with SemVer in the Rust community. Is that right?

Yes. So I’m the author of a tool called cargo-semver-checks, which is a linter for semantic versioning. So when you’re about to publish a new version of your package, cargo-semver-checks can check its API against the previous release, and just make sure that everything’s looking good, that you haven’t made any breaking changes, or that if you have made breaking changes, the next release is major, not minor. So you’re not going to break anyone in the ecosystem.

Gotcha. And Chris, your work with SemVer is what?

Basically, twofold. One, my biggest one was I spent a bunch of time – I worked at LinkedIn for about five years. And one of the things I worked on was TypeScript adoption at LinkedIn. LinkedIn is one of the few big tech companies that instead of doing a big monorepo for all of its code does a bunch of small repos and uses SemVer internally. And so as we were looking at how do you adopt something like TypeScript, we wanted a really good handle on how is that going to intersect with our frontend web development ecosystem using Ember.js, and using a bunch of repos. Using SemVer internally and then also externally.

[00:08:10.20] And so I ended up writing a spec, which lives at SemVer.ts.org for “What does it mean to try to apply SemVer to TypeScript?” Among other things because TypeScript ships breaking changes in what the Npm ecosystem thinks of as minor releases, and also because the type system of Typescript - and I’m sure we’ll get into more of this as we go, but it complicates what it means to make a breaking change in really interesting ways.

And that got me running down asking “What does it mean if you have things that borderline on fancy types that only show up in Haskell or Idris otherwise, affecting how your SemVer spec actually works?” And then I recently gave a talk on this subject, which is how Predrag and I ended up talking with each other… In which I looked at “Okay, what is everybody doing?” What’s the state of the art for how to handle versioning, whether you’re in Ruby, or Node on kind of one end of the spectrum, or whether you’re in Rust, or Elm, or when you’re out there doing crazy things - and crazy in a very good way here, like what the Unison programming language can do. And just tried to say “Here’s kind of the range of options, and here’s where we might be able to go in the future by pulling in new ideas in computer science research, and so on.” So I’m at least as much – like, Fredrik and I are competing for who’s the nerdiest on SemVer here.

I think I’ve gone fairly deep. I’m mostly focused on Rust, and I also have done some prototyping on a similar SemVer linter for Python. So I think I’ve gone deep, and Chris has gone wide.

There you go. Well, if we were to stay shallow for another two or three minutes and got all of us on board with the foundation of what semantic versioning is, for the lay folk… Chris, do you wanna define it for people?

Yeah. The big idea is that versioning is a tool for communicating with the consumers of your package, or app, whatever it may be. And SemVer looked at this problem over a decade ago and said “There are a bunch of emergent ways that the software ecosystem has come up with to kind of describe the feel of these changes over time. It’d be great to give them names that we can all use to talk about them consistently, and to give those names some semantics.” And so semantic versioning, where the names are major, minor and patch, for the kind of typical number dot number dot number versioning scheme that, again, emerged organically over the course of decades prior to that. And so a patched version means “This has a bug fix in it, and it’s otherwise backwards-compatible.” A minor version means it might have bug fixes, and it has some new feature, and it’s backwards-compatible. And a major version means there are breaking changes in this. There might also be features, there might also be bug fixes. And then there’s additional metadata you’re allowed to tag onto the end, like “This is a pre-release version.” So 1.2.3-alpha-0 tells you it’s an alpha release. And you can stick build metadata on the end, or whatever else you want. But the big and important bit is that semantic notion of what major, minor and patch mean, and the shared vocabulary for talking about them.

And I think it’s also important to think about what SemVer means from a user’s perspective… Because when I maintain a tool, and I have a few hundred dependencies, I don’t necessarily want to look very closely at the various different version numbers and think about what they all mean. But what I do want to be able to do is say “Run cargo update inside my Rust project”, and know that because everyone adheres to what is a breaking change, and when it gets published as major and not minor, the updates that Cargo is going to make to my logfile are going to be backwards-compatible. They’re going to result in me still having a working building project at the end of that command. And this is not specific to Rust. I could also do the same thing for the Npm ecosystem, and TypeScript, let’s say… I could do the same thing for Python…

[00:12:28.16] At the end of the day, as a consumer of SemVer, I don’t care about the numbers, I don’t care about the meanings, I just want to not have to sign myself up for fixing things that I did not intend to break. I ran a command, I wanted some new dependencies that are compatible with my build, and I want that to be the case every time.

And notionally, you want that to have the same basic meaning even if you’re jumping across ecosystems. Like, Bundler, and Composer, and Npm, and Cargo, and all of these can use the same basic language, and so your experience in principle - we can talk about it in practice; but in principle, it should be the same. That you can give a specifier to your package manager and say “Look, only give me minor bumps to this. And I’ll take new features if they come in, and I’ll take patch versions for sure if they come in, but don’t give me any breaking changes.” And Npm should be able to say that, and Bundler should be able to say that, and I as a user should be able to say “Okay, cool. This is gonna work the same basic way. Maybe I need to tweak the syntax a little or think about a couple different things.” But it should translate across ecosystems, more or less.

And I love the choice of words there. “Should be”. Ominous. I think we’ll come back to that.

I mean, I think that’s the big problem. Go ahead, Adam.

I was gonna say, this might be the biggest question I have for this whole show, and it might be just transcending all the topics, but it’s like, is SemVer poorly adopted, generally, or poorly misunderstood, which makes it hard to fully adopt without pain? Because I feel like that’s kind of hard… I don’t fully get SemVer in all the ways; there’s a lot of – y’all are SemVer nerds, as Jerod lovingly called you guys… And so there’s a depth level that you all understand SemVer, that to me if I eject and do my job, and then I come back in and SemVer matters, I’ve forgotten all the things. And so I just wonder if there’s a lot of people like me - and potentially, Jerod, you aspire like I do with that same kind of thing… Is you eject, you do your job, and you kind of come back and like reminding yourself exactly all the implications of SemVer. So is it poorly adopted, and then generally to adopt it and to use it it’s hard, because not everybody does it the same way?

I think it’s a tooling problem, to be perfectly honest. If any kind of technology requires that you earn a PhD in it before you can be a proficient user, then it’s not going to go very far. And the rules of SemVer in Rust, and Typescript, and Python - they’re not obvious. I mean, I’ve been doing this for years, and there’s hardly a week that goes by that I don’t learn about a new horrifying way to accidentally cause a breaking change in a Rust project, in a Python project, in a TypeScript project.

So the fundamental problem here are that the rules are too complex. There’s too many of them. It’s too easy to break them without noticing. Chris and I have some fantastic examples of doing this in Rust and TypeScript, where the most innocuous-seeming change ends up being breaking, for some reason, that no sane human would ever think of. So the real answer here is the tooling just needs to be good. We can’t keep all of these rules in our head, and we need tools to do that for us. And so I authored one of these tools; it’s not perfect yet, but it’s getting better every day, and I think the name of the game is not to hold ourselves to an unreasonable standard of perfection, and just minimize the number of times that a developer publishes a change, and then wakes up the next morning to 100 frustrated users saying “Why did you break my project?” Because that’s a regrettable change that happened. We want as few of those as possible, and maybe we get to zero one day. I don’t think we’re there… But I think that trying to get as close as possible to zero is a valiant effort, and I think we can make a solid dent in that.

[00:16:16.13] I would say too in a multi-person project, though, you also have multiple people that can version, or have implications into the versioning. And who determines whether it is a bug fix, or a break…? You don’t always know, either. So you may unintentionally submit something to your repository or make a change, doing a release, or somebody does a release for you… I mean, there’s a million different ways it can happen. It seems like it’s such a brittle process, fraught with opportunity to fail.

I think that’s right. One of the things that I dug into really deeply when I was talking about this at a conference a month and a half ago is exactly that dynamic; that what makes this difficult is those semantics all sound good, but the definition of a breaking change gets fuzzy. So there’s a guy named Hyrum Wright, who works - or worked; I’m not sure if he’s still there - at Google, who coined a great law called Hyrum’s Law, and a co-worker of his put it up on the internet it; I think it’s at hyrumslaw.com. And it says in basically so many words “Given a sufficient number of users of any API, all observable behaviors of the system will be depended on by somebody.” And my observation is that the number of users is like a few dozen. It’s not many. And that can be used as an argument to say “SemVer doesn’t count, or is not sensible, because anything you do can break somebody”, and bug fixes will probably break somebody. This is actually the reason that TypeScript, the compiler, ship what I would think of as breaking changes in its minor releases. Because their argument from a philosophical point of view is any change you make to a compiler breaks someone. So it’s not sensible to have a discussion about what a breaking change is, or isn’t. Now, I don’t actually agree with that, but I do think it gets at something really important, which is that ultimately, we’re not dealing in something that’s purely a technical problem to be solved. Because ultimately, again, versioning is about communication, right? It’s about that conversation you’re trying to have with your end user; often a conversation you’re trying to keep as low bandwidth as possible, where they just see “Version number change. What do I need to think about it?” before they even go digging into your release notes… So that they can say “Oh, yeah. This one should be safe. Let me try it.” Bump. Worked. Cool. Great. And I move on with my day.

And so you have this tension of saying “What’s a bug?” If my bug fix, which is a real bug fix, breaks my entire user base, do I call it a bug fix any more? And I think what that gets at is that you still need human judgment in there, and what you want is for the tool to catch all the cases which are knowable. So all the cases where you can say “No, for sure this is a breaking change.” You want the tool to tell you that. And then additionally, you’re going to have things on top of that, which might also be breaking changes, where you’re saying “Okay, this thing might not be a breaking change in terms of that ridiculous 10,000-word spec I wrote about TypeScript”, or in terms of what Cargo SemVer checks can catch today… But I’m looking at it and I’m saying “This one’s going to break 96% of my users. I’m going to go ahead and call it a breaking change, because I’m the human in charge of this and I care about my users, and I’m going to make that judgment call.”

So it’s sort of a start with the things you can prove or know based on tooling, and that eliminates a massive amount of the argument or confusion in the space, because you can just trust the tool to do it, and then pull in the human judgment and say “Does this bug fix feel like a breaking change? Okay, then it is.” Because, again, communication tool above all.

[00:20:15.13] And this is interesting, because I think it’s sort of the opposite of many other linters, where false negatives are actually okay. Not only are they okay, they’re going to happen no matter how we feel about them, until we solve that pesky halting problem, right?

[laughs]

No SemVer checker can catch everything; human judgment is always going to be necessary. What we really strongly don’t want is false positives. The tool saying “I’ve found a breaking change” where that is not the case. So we want our tools to be extremely confident when they report something, and it’s okay if they don’t catch everything. And that’s the approach that cargo-semver-checks takes, that’s the approach that my SemVer linter for Python takes… I just think that’s the correct approach. And that’s not always the right approach for every flavor of linter, right?

Right. Sometimes the false positive is safer. But here, I tend very strongly to agree. And a good example is like what Elm does. Elm has a very conservative and fairly minimal approach to the way it thinks about it. It basically just looks at the changes when you do something at a type level. Did it add a parameter to the function? Did it remove one? Did it add or remove a field on a data structure? And very simple checks like that. And that’s all it does. But it does enforce that, and it’s built into the package publishing flow. And I think that’s great. Again, it leaves room for the human judgment, but it also sets a conservative baseline that’s guaranteed to be accurate. It’s never going to false-positive you, because any of those changes can be statically known. “Nope. 100%, you made this change. That’s gonna [unintelligible 00:21:50.10] somebody.”

Right.

Break: [00:21:55.12]

Let me make two somewhat contradictory statements, which I both think are valid… The first one is that I have been a user of SemVer tooling for many years, in roughly three different SemVer-oriented camps - Ruby, Elixir, JavaScript - as just an application developer. And roughly speaking - and I know SemVer enough to realize major/minor/patch, what should be sasfe, what should I check, bla bla, bla. I’ve rarely been bitten by these problems as a user. Now, you guys are at the library level, and you’re like the tooling level, and so… So very generally, the system isn’t terrible. Like, we are talking about its downfalls, and we are nitpicking, and there are many ways that it breaks, and the problem is we didn’t know it broke until it’s too late. In fact, we didn’t think it was going to be a break, and so we communicated wrong. These things are all issues, and ones that we all butt up against, in some places better than others… But generally speaking, I think it’s been somewhat of a win.

That being said, with the tooling situation, Predrag, that you’re talking about, and with our desire to constrain ourselves from miscommunicating, are we finding a local maximum? Are we trying to polish a turd, so to speak? Is there ever a solution to this problem? Is it worth saying “We can try to clean up the world that we’re living in, or we can just invent a brand new world”? Is SemVer just fundamentally broken, and we’re not going to fix it? And we could probably just do versioning in a different way. Some people just ignore it altogether. Your thoughts?

I actually have some hard data to share on this… So about a year ago, four enterprising college students and I embarked on a journey to figure out how good the state of SemVer compliance is in the Rust ecosystem. Now, Rust - statically compiled language, biased toward systems; you’d expect if anyone is very hardcore about it [unintelligible 00:28:31.06] properly, it should be Rust, right?

Yeah, for sure.

So what we did is we took the top 1,000 most downloaded Rust libraries… So these are extremely popular things like Serde, and Tokyo, and things like that; things that multibillion-dollar companies depend on, easily. And they’re developed by some of the smartest, most experienced people in the ecosystem. And we scanned 14,000 of their releases total. We’ve found that more than one in six of them has unintentionally shipped a SemVer violation that we could find. And we’ve found that more than 3% of all the releases contained at least one SemVer violation that we could detect and could have prevented. 3% of every release means that on average if I run cargo update once every 10 days, my project is broken once. Like, every 10 days that I run cargo update, I’ve updated one dependency that will introduce a breaking change.

Now, it’s unclear whether I actually use the part of the API that ended up being broken by that breaking change or not. So I might get lucky and it might be okay. But now let’s think about this on a population timescale. There aren’t 10 people depending on these libraries. When these breaking changes happen, they break the entire ecosystem. And so thousands of people have to spend their time figuring out why the heck the build is red all of a sudden.

[00:29:57.22] And then maintainers on the other side have to all of a sudden, in an unscheduled, highly stressful, highly charged environment, publish an emergency patch that either undoes the thing, or whatever it was… Imagine if it was bundled with a security fix. Now all of a sudden the pressure is even worse… So as a user, when I run cargo update, I don’t want to be broken. As a maintainer, when everyone cargo publish, I don’t want to break people. As a member of this ecosystem, I want the people that don’t have a lot of time on their hands to not spend their time pointlessly fighting tooling in one way or another. I want all of the maintainers of all of the libraries that I depend on to not have to worry about “Were they broken, or were they going to break somebody?”

So the thing here that we’re talking about sounds like a small effect. 3%. Yeah, whatever. Rounding error, maybe it’s okay. But the thing is that it adds up. These little bits of friction here and there, they really add up when you consider the large population and the long time horizon of these projects, and how many times these projects get downloaded and used every week. So even though the individual impact on a single person’s day is probably next to zero, multiply that out to the number of people that depend on that software, and all of a sudden the impact is pretty darn significant.

Okay, so you’re saying it’s not good enough as is, and tooling can improve… Can it get us from 3% down to a miniscule fraction in which we can live with this? Chris, your thoughts on that?

I think the answer is yes. And one of the reasons that I’m more bullish on sticking with SemVer and putting tooling around it is because I did survey the rest of the world as it were when it comes to versioning, and there are a lot of approaches that just say “Ah, these problems with SemVer are fundamental. Scrap it.” One of them, SoloVer, is just have one version number - it’s 1, 2, 3, etc, just go up. And that has a certain appeal to it, but the actual fundamental issue there hasn’t changed. All it does is take the burden off of the maintainer of a library, and put it on all of the users. It says “Okay, now you’re responsible anytime any one of your dependencies changes, including transitively, anywhere in your dependency tree. So go read the release notes”, which tend to encode things like breaking changes in the release notes. Because again, communication problem, right? We want to know “What did this do?” Also, as an aside, all of those proposals include things like “Well, you can also stick like pre-release numbers on the end.” And I’m like “Hold on, hold on… It seems kind of like we’re backing our way back toward this whole SemVer thing now, aren’t we? Shouldn’t your pre-release just be another number?”

I think there is a sense in which there is a maybe fundamental local maximum. Maybe it’s local, but the hill’s so big that we’re not going to find a different path. I could be wrong about that. But when I go looking around, the things that seem like they might change the calculus here don’t so much eliminate the value of SemVer as they do build on it. So a good example here is what the Unison programming language does. Pretty small language, but it is aimed at industry. It’s not pure research. And they do something that’s really wacky, in the best way. You don’t store your code as plain text. Instead, they take advantage of the fact that they’re a pure functional programming language, with really well specified semantics, and they say “Okay, we can take your code, normalize it, hash it, and store the compiled output of it with a pointer to it”, which means a whole bunch of interesting things… But for the purposes of versioning means when I make it breaking change, the original version is still there, because that hashed, compiled version of it got committed to a database instead of to plain text. And that database version is what anybody who depends on it sees.

[00:34:06.12] So when I add a new parameter to my function, the consumers are still pointing to the old function, which means they can pull this update and say “Okay, I can progressively switch over to the new function signature, but I can do that at will, and the two can live next to each other”, and because it is a pure functional programming language with no side effects that aren’t managed off in the runtime, etc, etc. You know, leave all that aside. Suffice it to say because of that choice, they can just “ship a breaking change” without ever breaking anyone.

The reason you still want SemVer here though is because SemVer is a communication tool. And so SemVer lets you say, “Okay, there are these new features in the library. Here’s a bug fix. You’re going to want this one.” And even though that means you need to actually go update which compiled version of this function you’re pointing to, you’re getting data from that, and when you go to publish your library, you want to be able to use that information. Even knowing that it’s not going to break your users in the same way, it does let you then say “Oh, I didn’t actually mean to make a breaking change here. I wanted this to be compatible and to just keep working forward.”

So things like that, I think, are pointers in the right direction. There’s also a couple of papers out there from folks at the Nova University of Lisbon, who are asking “What happens if you bake versions as types into Java?” Java because it’s the kind of default language to do this kind of research on. Their proposal is very interesting from a type theoretic and versioning perspective, and would never get adopted in industry in a million years, because it’s just way too much boilerplate… But it does the same thing we’re talking about; it bakes this notion of backwards compatibility in, in a way that I think if you were going to actually ship something like that in an industrial programming language, you would actually want SemVer as basically how you do it. And their type system that they slap on top of Java, effectively encode SemVer with keywords. It’s upgrades, and replaces, and things like that.

So I think there’s work to be done here, but I don’t think it’s going to be in the near term, for one. So we’re going to need the tooling. And even if and when we see something like that type system on top of Java, or what Unison is doing, becoming more widespread, I think those kinds of things lower the risks in really interesting and important ways… But they would still really benefit from the kinds of tooling that we’re talking about. They also though highlight, I think, one of the things that’s easy to miss in these kinds of discussions, which is a lot of times people like me, who are type theory nerds, etc. like to go looking for that kind of a solution to a problem. It has two limitations. One is that’s never going to work for Ruby. I say “never”, but you could imagine a world in which type adoption for Ruby is at 100%, but that world seems very unlikely to me… Not least because a lot of people who love Ruby love it because it’s dynamically typed.

And second, doing all of those things purely at that, like “Let’s bake it into the type system level” has costs, because it turns out that itself then becomes a thing that you need to think about in terms of the versioning of your language. Because one of the things that shows up is that the more foundational, whatever your tool is - like, if you’re an app, and you just have consumers, it’s not that big of a deal. Your versioning is basically purely marketing. If you’re a library, you have a bunch of apps that use you and maybe some other libraries. If you’re a framework that everybody else builds on, how well you do this now affects everybody else in the entire ecosystem. If you’re a programming language, you’re kind of doing it at the maximum level, and you still have to communicate those versioning constraints to other people. And the more complicated your type system is, the harder it is to actually understand what the implications are for versioning.

[00:38:09.28] So the tendency that people like me have, to say “Ah, bake it into the types, and it’ll be rigorous and checked forever” can actually undermine your net goals here, because now you’ve made it harder to think about this fundamental communication problem.

And this is an area where Chris and I had an interesting discussion that made us feel like this would be a good podcast topic. I agree with him that building versioning into the type system of a language like Ruby or Python is not the way to go here… But that doesn’t mean that we can’t build SemVer tooling for dynamic languages. And in fact, I’ve built a Python semantic versioning linter that’s in closed beta with a few friends right now, but if someone listening to this wants access, find me on social media; I’m very happy to send it your way.

The core idea is that when we as programmers look at a piece of code, and a change to that code, we have a set of rules and heuristics that we run in our minds to determine if something is breaking. A public function got deleted. A function signature changed, now it takes a different number of arguments. A default went away from somewhere. There’s nothing stopping us from encoding those rules in a machine-checkable way, and just have the computer go through that checklist and make sure that we didn’t mess anything up.

Now, this tool isn’t doing anything differently to what we were doing, and if it finds nothing and we find nothing, then great. Just ship it as a minor, and everything’s fine. But on the off chance that we haven’t had our caffeine that day, or we’re just tired, or we have something else going on in our mind and we miss one of those things and the tool catches it, that’s just pure win. So it’s those cases that I’m very interested in catching with tools. It’s fine, they’re never going to be perfect. I’m not perfect either. I’m just very interested In finding the spaces where my imperfections and the tool’s imperfections happen to not overlap, so that at least one of us catches it.

Mm-hm. I mean, I like that because it’s pragmatic, and because it seems achievable. I mean, it’s just work. It’s probably hard work, and I’m glad you’ve put some of that work in… But it’s really like taking that 3% - which is probably a high watermark, considering it’s the Rust ecosystem…

It’s probably a much higher percent in other places.

It’s actually much higher in Rust as well, because the issues that we’v found by running the tool… If you use the tool, the 3% goes to zero, because the tool would have caught all of that and prevented it. So that number could only grow, even in Rust, because the tool gets better every day. So…

How comprehensive is the tool? Do you have like a set number of things you’re checking, and you’re just adding to that constantly? How’s it work?

Absolutely. So we have about 80 lints right now. So that means they’re 80 flavors of breaking changes for which if they happen, we could construct a program that gets broken by that breaking chang. So we could constructively prove that something bad happened. Either that program doesn’t compile anymore, or it triggers undefined behaviors, so it would be caught by Miri, Rust’s undefined behavior scanner, or something like that. So something very obviously goes wrong based on the thing that we’ve found, and we can offer constructive proof of that.

I also have a list in an issue on the project’s GitHub of like 150 more things that we should catch, that we don’t catch yet. And on a fairly regular basis, new contributors to the project come in and they go “Ah, this bit me last week. I’m going to check this thing off the list.” And they come in, they write a lint, they open a pull request, we merge it, and the next release that problem is never going to be a problem for them or for anyone else ever again, so long as they remember to run the tool in CI before publishing, or on their local machine, if that’s how they publish. So it’s more of a never again kind of a tool, and not really a perfectionistic, idealistic sort of approach.

[00:41:58.29] Yeah, exactly. Yeah.

I love it. So that knocks off Rust, though. And you’ve something you’re working on in Python. But of course, there are many programming languages [unintelligible 00:42:08.16] and many ecosystems, some of which nominally do SemVer. I mean, it’s also the ones that just don’t do it. I mean, that’s the other thing with – first of all, you have to buy into SemVer, but then also, the communication of how you do SemVer… Forget the tooling. Just like - I’m sure there’s different ideas of what SemVer is, even in the minds of one person, probably… You know what I’m saying? Like “Yeah, we do SemVer, except for for the majors, because we want to have a marketing opportunity.” So we throw it out the window when it comes time to market… Because that’s a communication problem as well, and one that many people dislike SemVer because of, right? They can’t market their big releases.

Yeah. I actually think that is the biggest unsolved weak point of SemVer. Like, more than the philosophical “What is a bug fix problem?”, because that one’s really one that we can just agree, kind of make a definition, make a contract and run with it; everybody can get on the same page. Maybe I don’t like the contract you picked, but I can at least understand it and follow it. The fact that versions are also a tool for marketing makes it very difficult for projects which do SemVer, and also do breaking changes to figure out how to use their marketing.

Now, Rust itself as a language has just said “Oh, we’ll never ship a breaking change. Problem solved. We’ll tackle marketing in entirely other ways.” Ember.js famously is like the most SemVer project ever, and has struggled enormously with this, because the JavaScript community looks at big releases and thinks “Ah, this is Ember.js 5, or Ember.js 6 coming up in however many months from now. Sweet! Big, cool features.” Because when you look at React 19, you get a bunch of cool features with it. A ton of stuff. And some breaking behaviors that slowly built up to over the course of the React 18 lifecycle. But then you get to Ember 5, and it says “No new features.” And you say “I’m sorry, what?” Because Ember says “No, the only thing we use major versions for is removing deprecated code.” And in some ways, that’s very powerful, because it means that you can think about how you schedule in updating to minor releases, updating your long-term support minor releases… And then okay, if an Ember release comes every 18 months, I can just bracket in, “I need to have these deprecations cleaned up by time X”, whatever that ends up being. That’s really powerful. And you also know that you’re not coupled to the existence of these new features for “I have to get through these breaking changes first.” That’s great. But how do you tell that story? And I would put it as a former Ember framework core team member who thought about this a bunch… We never figured it out. Ember tries to use additions to do this, which is similar to Rust’s notion of additions. It’s this kind of big “Everything comes together at a point in time. We’ve updated all the documentation. All the APIs are coherent. Everything is in a place where we’ve made this really significant change to the ecosystem.”

It’s a good idea that in the Ember case I don’t think has worked. I think it has sort of worked in Rust’s case, because it comes with other trade-offs around being able to make changes to the surface syntax of the language that are opt-in, but breaking and backwards-compatible… That’s a whole different podcast and a different discussion. Super-cool, worth talking about. But I don’t think we have a solution for this particular dynamic of if we’re going to SemVer seriously, how do we communicate “Here’s a big marketing moment”? Ember’s additions is, I think, a step in the right direction there… But I’ve sometimes wondered if we don’t need like marketing version as just a totally different thing. And I think that’s what additions points at, but I don’t know. I think it might also be a matter of seeing somebody with enough scale try it… Because Ember is very small. And it has a lot of great things going for it, but if you’re 2%, 4%, whatever, of the broader JavaScript ecosystem, trying that kind of thing… Whereas if React did it, and did it that way, I think it might stick more in people’s minds of “Oh, this is how this works.”

[00:46:26.27] Now, I don’t expect the React community try that, but you can imagine that a bigger project might be able to influence the way people think about versioning more effectively by taking that swing… But it might just also not work, and we’d have to try something else.

I also have a maybe strange idea for how to maybe fix this. What if we made breaking changes not actually require major versions, and still adhere to SemVer? So we could actually reserve major versions for marketing only. Crazy, I know. Hear me out. And just to be clear, I am not planning on building this until there is a lot more funding flowing to cargo-semver-checks than the other projects I’m working on. But it is a genuine idea that I think could work. Imagine that we could detect with very good confidence breaking changes in a mechanical fashion. And based on the change that we see - like “This argument is now necessary in functions, as opposed to previously we could pick up a default”, whatever. If we could generate code mods, rules for how to change code so that it’s compliant in the new version, and preserves the behavior of the old version… Where when I run cargo update, or the equivalent in Npm, or PIP, or Poetry or whatever, in addition to picking up the new version, it also goes in and tweaks my uses of the code that it updated dependencies for, so that I’m no longer broken. This still satisfies my objective if the build is still green before and after. And there was a breaking change in the API, but that breaking change only affects me the next time I write a piece of code that uses that API, and not any of the existing uses.

So in my book, if you ship code mods that are of sufficient quality for all of the changes that are breaking, that does not necessitate a major change to adhere to SemVer. Because no human action was involved in making that change. That change is completely mechanical. And at that point, we can say “Major versions are for marketing, and everything else can be minor.”

Now, obviously, this requires a bunch of tooling and infrastructure that we don’t have, and tooling and infrastructure that I would love to build if someone wants to send a lot of money to my GitHub Sponsors page. But I think it’s a viable future that we could have, that solves both this problem and a lot of ergonomics and usability problems of SemVer as well.

That’s interesting.

It’s a cool idea. It very much feels like a boil the ocean kind of a thing.

That’s what people said about cargo-semver-checks when I started it. There was a lot of “This will never work.”

I think one of the things that strikes me about that is that it works if and only if you can make it, as you said, genuinely 100% on the code-mod-ability side of it. I think it gets at the human communication dynamic piece of it, again. One of the things I think about a lot is “Okay, if this operation used to be–”, you know, to use the standard performance language, [unintelligible 00:49:29.03] Constant time access to get something out of this data structure. And now I made a change to the performance of that data structure that gives it way better memory characteristics, but now makes it [unintelligible 00:49:41.28] so it scales with the size or the number of items in the data structure, and the surface-level API didn’t change - that’s a breaking change in my book… Because you can radically impact the performance of your end users’ systems by doing that. But it’s not going to be statically catchable by the kinds of things that could code-mod it, because the API hasn’t changed.

[00:50:07.13] So I think there’s an interesting chat – like, I think what you just described is good, and it’s actually kind of the improved version of what Rust and Ember and some other JS frameworks have tried to do, of when we ship a deprecation, we also ship a code mod with it, that moves you to the new API. And that goes a long way… Especially if it’s one of the ones that’s 100%, rather than like 80%. And now you have to do the last 80% yourself. And yes, listeners, I said 80% both times on purpose, because that is my experience of how that goes. You solve that 80%, and the last 20% takes another 80% of the time.

I think the other challenge there is making the thing trustworthy enough that people feel like it’s safe to do that as part of their Npm update, or cargo update, or whatever it is. And I think you probably have more leverage to do it in an ecosystem with types, than one that is without types, for the simple reason that it’s easier to be confident that you got it right with that code mod. Not necessarily impossible in Ruby, or JS, or whatever, but easier.

It seems like for incremental adoption – I like the idea, Predrag, but it seems like for that, just simply adding a prefixed… I mean, Chris is kind of joking, but it maybe would work kind of – like, why don’t just add another number, that if your version has four numbers, the first one is marketing. If it has three numbers, then it’s just the old style. And so you could just have that, and not having to fix that problem of the breaking change or the code mods thing… Which I still think would be a worthy goal, and one that you should definitely get sponsored by somebody to work on. But it seems like it’s really far away from something that we could use.

Would it always remain like one in front of that? So if you had four numbers, would the number one or number two be – because that’s the thing too with marketing… Not to say negatively about those folks, but they’re not generally in the minutiae like we are here. And there’s a thought process break, where what they think is marketing is simply what is acceptable, what’s praiseworthy, what’s celebratory, not so much practical and pragmatic in the application state.

Right.

So it’s really just window dressing. It’s not really useful. It’s just there to be there, to represent.

Which is why it’s optional.

There’s almost a sense in which the ordering is the opposite of what we want for marketing reasons…

…in that the minor release which comes along, whether it’s on a cadence like Rust, or Chrome, or whatever, where it comes out on a predictable timeline, or where it comes out whenever it comes out… The minor release, if you think about the SemVer semantics, is actually the marketing excitement. It’s the “We’ve got new stuff” moment. And yet it’s the second slot. And the “Something broke” is the first slot, because really – there’s a good reason for it, which is it’s the one that’s going to hurt the most. Hopefully, the other ones won’t hurt. But doing the breaking changes is going to be some work. That’s why it’s breaking. I don’t have a solution for that, but it does feel like they’re backwards that way. And in fact, when I was making slides for that talk, one of the things I noticed is that when I went to explain it - and I think I did this when I was talking about it earlier - you want to explain the order upside down. You want to start with patch, and say a patch is a bug fix. And then a minor is a feature or a bug fix. And then the major is breaking changes, and maybe also a feature or a bug fix, but you almost end up kind of backwards from the SemVer ordering…

[00:53:52.00] And I do kind of wonder if the move – to your question earlier, Jerod, about “Is SemVer even the right shape?”, I think if we were to take a different swing at it, we want to keep all of those bits as it were of data, but we might want to find some way to treat them as a different kind of ordering… And getting really nerdy here for a sense - it’s not clear to me that when we think about like partial orders, or total orders in a deep type theoretic sense that that’s actually quite the right model for versioning. And the marketing part becomes a piece of that, because the marketing thing may really be very distinct from one breaking changes land. And both of those are really important bits, but it feels really weird to say that I have version 2. (where that’s the marketing version) 4, where there’ve been four breaking changes as part of marketing version 2? That feels weird. And then all of these other feature numbers along the way as part of it.

And maybe just Thinking about different ordering schemes or ways to kind of decouple those further could be a part of the future here. You need all of the bits, though. You need the marketing bit somewhere, and you need the breaking change bit somewhere, and you need the bug fix bit somewhere…

One more thing that’s interesting is that there’ll be names… So like Dragon Fish, for example. Not only is it – and I’m talking about TrueNAS here, in this case. TrueNAS SCALE, 24.04, which actually is not SemVer, I’ve learned today; it’s called CalVer. It makes sense, but I didn’t consider the fact that it’s the 24th here, and the fourth month, so they have a release cycle. Everyone understands that. That’s also – they’ve adopted the Ubuntu way as well, which is year and month, and they have a release schedule… But they also call it a name. And in the case of Ubuntu, they have certain names; I think they’re all based on Pixar, if I recall correctly… And then Dragon Fish in the case of TrueNAS SCALE, this is what they named it. So they add one more nomenclature, which really is their marketing term. So I wonder, is the fourth number really required? Can you just leverage a naming schema?

That’s what Ember has tried to do… And again, that’s the question of “Does that work or not?” I think Ubuntu is interesting as an example. I’m not in that ecosystem anymore. I used to run Ubuntu for a bunch of things, but I never – even when I did, I don’t remember remembering the names very well. I remembered the cadence. But CalVer is interesting - CalVer being calendar-based versioning - because once again, you’re back to read the release notes as your mechanic for “Did this break something that I depended on?” Because CalVer tells you when something happened, but it doesn’t tell you what happened then. It’s in that way kind of like SoloVer, which says even less. So I like CalVer better, because it at least gives you a date that’s useful… But both of them have the problem of they basically reduce to “Something happened.” Right, but what? What happened is what I want to know. And I think the big name for it, the branding name for it, as you’re saying, Adam, it helps…

Because then you can actually use something. And I was incorrect. I think it’s Debian that leverages Pixar characters, right, Jerod?

Yeah, Toy Story was where they started. I’m not sure if they’ve moved on from there.

And I don’t know… I think Ubuntu actually has a double name. I’m trying to figure out quickly, because I messed up.

Yeah, they’re like alliterative animals, aren’t they?

Yeah. Yeah, alliterative animals, exactly.

Right. Warty Warthog was 4.10…

Warty Warthog, I remember. But I do agree, I never go back and remember which one was – now, I know Sid, because that’s the experimental branch. That’s a great for that. But then like the rest of the Debian releases, it was like “Was this Woody? Was this Buzz? Was this–” I don’t know, because they’re important as they come up, but historically, I just forget which one’s which… Because you have a bunch of them over time, so they kind of lose their meaning.

I think CalVer really has a place with products, less so than with libraries. Because I think just like knowing – it is nice to know when was this product released, because it’s right there in the version number… But I agree, it really doesn’t tell you much about that release… Which is fine for products, because you have all kinds of other things around them. But with libraries, we want to know more details. Like, this is like business, not fun, kind of stuff. We don’t want to break our businesses.

[00:58:21.05] And for libraries and frameworks, I think of SemVer as CalVer with an extra optimization slot, in a sense…

[laughs] Okay…

Think about it this way… If every CalVer release - you release twice a year - is allowed to make breaking changes… That’s just like as if you updated the major number in every release. But every so often, there’s a release that does not make any breaking changes. Maybe it’s just some patches, maybe it’s some new features that don’t affect existing functionality. If you tell me that you’ve made such a release, I don’t have to pay as much attention when upgrading. I will expect that I can just let my package manager from my ecosystem handle it for me, with no action from my part. Now, you could be conservative. You could say “I’m not sure, maybe I’ve made a breaking change” and release a new major every time. And that still adheres to SemVer. That also happens to be SoloVer in practice. But if you tell me that you’ve made breaking changes more often than strictly necessary, then you’re shifting the effort on my part, like Chris was saying earlier. The more you can tell me that based on your gut feeling you have not made breaking changes, and ideally based on the best available tooling for your ecosystem, you also have not made any breaking changes, the more often I’ll be able to patch feeling confident about it, and ideally not spend six hours then chasing type errors or worse, misbehaviors in a system that I’ve deployed to production. So I think SemVer is just an optimization over what we could get otherwise, because I optimize away the necessity of me reading the release notes religiously for every one of the 400-odd dependencies that my project depends on.

I do think that is suggestive though of the value of calendar-driven releases. I think predictability in versioning is actually really, really helpful in that regard. So the fact that TypeScript releases basically quarterly is really helpful for planning around it. The fact that Rust releases every six weeks is really helpful for planning around it. And it’s helpful for all the teams who work on those projects, because it gives them a schedule, but it also means that you can think “Okay, maybe we don’t have the appetite as an organization to take a TypeScript update every quarter.” But knowing that they come out quarterly, I can bundle it up and say “I’m going to do this every six months” or “I’m going to do this every year”, or whatever the cadence I choose. And it’s very difficult to do that in ecosystems where that kind of schedule doesn’t exist.

So historically, I don’t think C# has – and they may have shifted this in recent years, but it wasn’t the case for a long time that there was a predictable cadence. Like, they came when they came, and that meant you couldn’t plan around that. For a long time Ember didn’t do that, and you couldn’t plan around it. React certainly doesn’t. React 19 comes when react 19 comes, and the entire ecosystem gets to go “Whoa, let’s figure this out now! Okay, this is gonna be a ride!”

I think having a schedule – one of the things that Predrag just said that got me thinking is it would be really interesting if your major version intentionally adopted the calendar versioning nomenclature, because that would pull you out of the frame of thinking about it as of a marketing version. Because 24-04.1.2 doesn’t make you think “Ah, the marketing version 24-04. That’s not a marketing version. No. And that might be enough of a shift, and you could still codify that like “This is a system that tools know how to process as SemVer-ish in shape”, because it’s still predictable, it’s still month/year with a dash in between them; it’s trivial to parse. But doing that might give you as an ecosystem a chance of much more clearly distinguishing “Okay, this is a timed release that bundles up the breaking changes since the last timed release”, and in effect doing the kind of thing that Ember is trying to do now, and that others I would like to see try to do for breaking changes, where they just - Angular does this, Node does this; lots of things do this - lean into it.

I think Angular and Node could do exactly this, and instead of saying it’s Node 22, you could imagine, especially since they have a six-month cadence, that it’s Node 24- whatever. I don’t remember when the next one comes out. I think September, or something. So 24-09. And then the patch and feature releases in it - that could be a really helpful way of distinguishing the marketing dynamics.

Break: [01:03:03.12]

Let’s draw a corollary here potentially, because arguably, Ubuntu has become one of the most popular flavors of Linux to install. A lot of people use it. They were the first to come up with LTS. They do use this nomenclature you just mentioned. I just wonder, is there a reason potentially beyond the quality of the release cycle, the team behind it, what they’ve done, etc. has their ability to popularize LTS’es to be more trustworthy over time? …as well as this nomenclature of 24-04, 22-04, 20-04, however it frames out - do you think that’s been a corollary to their success?

I think there are two values there. One is the clarity in communication, and the other one is the clarity in scheduling, both internally and externally. So the communication that LTS says “If you use this, you are not on the hook for worrying about security patches. You will get those security patches for a predetermined period of time, and so you know what you’re signing yourself up for.” Right? And being able to credibly make that commitment is valuable.

The other half of it is the schedule certainty, both internally and externally… Because I remember some time ago I was going to build a new computer for myself, and I looked at the calendar and I was like “Oh, what I should do is I should wait for another month, because there’s going to be a new LTS release out. And so I don’t want to set up a brand new Linux install when I’m going to have to go through the whole process again in a month.” And knowing that that calendar is dependable, and is going to happen on a predictable cadence, is very, very valuable.

I imagine for a large project like that it’s also very valuable internally, because if you’re trying to ship a new feature, you don’t have to worry about “Will I make it into the next release on time, depending on when that next release is?” You know when that next release is, you know when deadline is… It’s the same deadline every year. And so where you stand. You don’t have to do any horse trading with the release manager, or pm, or whatever, and exchange favors, or whatever else needs to happen. I think it eliminates and streamlines a lot of that effort that normally has to happen whenever you’re trying to wrangle a big project toward a release. And I think that’s useful both for the project itself, as well as for people depending on it downstream.

At the same time though, even with your scenario where you want to wait a month, because you know the date, at the same time if you have confidence in the upgradeability from one version to another, with the .10 release in particular… Jerod, you know I’ve disclosed the behind the scenes some fear, like “Oh my gosh, I’ve got to upgrade my Ubuntu. I’m gonna wipe the system. I’m gonna backup and I’m gonna start from scratch.” But ‘ve learned and I’ve come to trust their ability to architect good software to give me an upgrade path… And it’s also – this is probably specific to Linux, that as soon as you log in, it lets you know “Hey, you’ve got an upgrade available, a new release, whatever it is, for the command you can run.” I’ve learned to trust that. So at the same time, I might be “Well, my 20, whatever, 21.10 of Ubuntu can easily upgrade to 22.04 safely.” And I’ve done it once before, and I’ll do it again. So there you go. So I won’t wait the month, because I have trust in their ability to release properly.

But I do think it really comes back to the beginning of this show, when you said SemVer is really about communication. It’s a machine-readable way to communicate for both machines and humans, that communicates in a lot of ways. And the challenge really is how well do people adopt it, and are there different permutations of it that misalign - or unaligned, I suppose - that makes it challenging to do SemVer? And like you all said before, if you have to have a PhD in it, is it really worth doing?

[laughs]

And that’s true, but I think, coming out of this conversation, I’m not thinking SemVer needs to die, by any means. I’m thinking it needs to live, it needs to have people like you behind it, but it is super-challenging.

And I think, to your point there, the more that you can make those kinds of transitions seamless… This gets at exactly the kind of thing Predrag was describing around code mods; even if you are shipping a breaking change, like even if you don’t adopt the slightly more “I’m still mulling on it” proposal there, of “It’s not breaking as long as the code mod is good enough”, even if you don’t do that, if every breaking change you make, every deprecation you make comes with a good enough code mod, so that when you do actually make that breaking change, you could have the kind of confidence that you’re talking about, Adam, that you could know that when I go from React 19 to React 20, or Laravel, whatever the current major is to the next one and so on, that it’s going to come with that kind of automation, and that becomes a norm across the ecosystem, or at least for those foundational pieces…

[01:10:10.01] I think that would give you a lot more confidence to say “I can take this upgrade that is going to be breaking, but I’m not going to be broken by it, because the ecosystem has built this value around this socio-technical contract we’ve set up with SemVer. And we’re going to apply all of our technical skill to make the social part of it better.”

I also think there’s a lovely positive feedback loop going on here. We’ve noticed this – I think we’re all on the same page that releasing your software more often is better than not. I’m thinking in the context of say like a SaaS application. If you release once a year, it’s going to be a much more painful release cycle than if you release every week or every day… Because big changes are hard to test, and small changes are better.

I think there’s something similar to be said about software and the rate at which you apply upgrades. If you accumulate 400 version upgrades and try to do them all at once - I mean, good luck to you; I just don’t want to be anywhere in your blast radius. If you try to upgrade them once a week, or even once a day, and you have high confidence in the tooling, then good things happen downstream of you, so more good things happen, and so on and so on.

The primary method by which cargo-semver-checks, the Rust SemVer linter I built, gets adopted is that somebody has an accident… You know, rolls a natural one, and – right? I mean, it really is an accident. Like, they didn’t mean to cause a breaking change, it just happens to them. And then they break somebody. And then somebody opens an issue and says “Hey, you broke me. This is bad. You need to fix this.” And a lot of the time someone says “Oh, by the way, cargo-semver-checks catches this. And then no fingers are pointed, no blame is assigned, but you very quickly see that project adopt cargo-semver-checks for the next release. Right? And if they adopt cargo-semver-checks and they ship fewer breaking changes, then libraries that re-export their API and rely on it to provide some capabilities downstream, they get broken less often, they might also adopt cargo-semver-checks because they see it works well, and all of a sudden a rising tide lifts all boats. Everyone’s software is better off, we can all be updated more often, so we all benefit from the new features faster, we get better performance, we get security patches patched more promptly, and not festering open for months while we try to figure out how to navigate our way through all of the changes that we’ve accumulated, because we haven’t updated any piece of software since 2017. Right?

Right.

I mean, I wish I were making this up, but these are things that happen in the real world. Wasn’t there the case that a large number of government machines all over the world, they’re still running Windows XP, right? They don’t do that because it’s fun.

Why do they do it though?

It’s very difficult to upgrade.

Okay. I want you to say it, that’s all. [laughter] To be super-clear. Yeah, it’s hard to upgrade. So when you trust the upgrade path, it’s a little easier.

But that comes with maturity, too. XP was super-old.

It’s important that you not just trust the path, but you walk the path regularly, because then you have smaller downstream effects, because you have less change every time that you make that change. I think that’s wisdom.

Hasn’t Microsoft Windows been notoriously hard to upgrade every new major version? Until maybe more recently, when it’s been less hard… Whereas like macOS, for example, which is a popular developer machine, has been generally easier to adopt new versions, and they made the upgrade path semi-easy. For the most part, pretty easy.

Interesting. I’ve had the opposite experience, personally.

Your mileage may vary.

[01:13:53.01] Developer machines might be different. I mean, if you’re installing specialized tools – we feel that, too. We do audio production, so there’s times whenever… I literally, I think on my main machine I may have just recently gotten – and I don’t even know what the latest version of macOS is, so I’m not a great example of it. I usually wait one year, to a half a year, just to make sure all their kinks are out… Because Adobe is always behind, and Adobe is our suite of production tooling, and so I know there’s some level of latency between the two… And I just can’t risk it, because I want to make sure you all sound amazing, our shows are awesome, they go out on time… And I can’t be the one that’s the bottleneck on those things because of my desire to be on the latest macOS. So that aside, I do think the upgrade path generally is easier in my experience with macOS.

I think that actually gets at something really important that we haven’t touched on so far, which is that a concern for backward compatibility, which can make upgrading easier, in this context the fact that you can still run 16-bit DOS applications on Windows 11 - which is amazing in a lot of ways - also makes it much harder to move the platform forward in other ways.

Apple, by contrast - like, you want to run an application from the 16-bit DOS era? Ha-ha-ha! Maybe if you’re running it in an emulator written in JavaScript you can do that. Cool. But on the operating system, absolutely not. On the other hand, that’s part of what makes it really easier - I’m not going to say easy, because it’s an enormously hard task… But easier for Apple to do something like ship an architecture transition from x86 to ARM, and pull off the whole thing in a matter of two and a half years, three years, because they’re willing to just cut their losses for backwards compatibility sometimes. And we see the same thing for a higher level of the stack, that a framework that is willing to make breaking changes on the regular can move forward faster, but that’s more cost for the people upgrading.

By the same token, somebody who’s really committed to backward compatibility can bring along all of the people in their ecosystem very well, but it can often be much harder for them to get the benefits of trying new things. And I think here actually the example of React and Ember in the JS ecosystem is a really good example of this, especially – it’s less true now; React has gotten way more thoughtful about breaking changes. But in that kind of mid 2010s to 2018, 2019 time, React would ship breaking changes much more than Ember would. And React got to try a lot more things than Ember did.

On the other hand, I’ve talked to a lot of senior engineers at very large companies who got stuck on React 16 and never moved on. And that’s less true for Ember, but Ember got to try a lot fewer things along the way. There’s just a fundamental tension there, that then gets manifested in your versioning. And the versioning is often a symptom, rather than the cause in that case. It’s telling you a true story, but the story it’s telling you might be telling you something else about the dynamics. The fact that Windows can still run DOS 16-bit applications has other consequences for the kind of incoherence of the Windows ecosystem. But that’s a really big benefit if you’re an enterprise IT administrator who wants to not spend a lot of cost on upgrading applications that just work for you. Trade-offs, all the way down.

I also think that good tooling on the part of language developers can massively tweak whether this problem is insurmountable or not that bad; it’s always somewhere on that spectrum, right? Rust has an interesting feature that I really did not appreciate fully when I was starting out, which is that you can have multiple major versions of the same library in your project exist at the same time. And Chris, you were just saying, this is not a decision that was taken lightly, and is not something that is available for free.

[01:18:05.04] For one obvious consequence, it makes the version resolver much, much more complicated, because it now needs to solve a problem that is much bigger in scope. But what that means is that if – you know, Chris, you wrote a library that depends on foo version one, and I wrote a library that depends on foo version two, and now Adam wants to use both of our libraries. So long as our libraries are not something extremely fundamental, like a framework like React, that expects to be in full control of everything going on in the environment, Adam can probably just use both of our libraries and be completely fine with the fact that the two of us can’t agree on which version is the correct version of this dependency, that Adam doesn’t know or care about. Right?

Now, this is also not for free to Adam, right? It means that when Adam compiles his project, he has to compile two versions of this dependency, and not one, so maybe his binary size is a little bit bigger, and maybe there’s a little bit of bloat in the development environment, and stuff like that. But the alternative is Adam having to pick between your library and mine. And I can tell you from the Python 2 v3 transition, that is not a pleasant experience to be in.

[laughs] A bad time.

I mean, I lived it, I made good software money just like moving stuff from Python 2 to Python 3… And if I could have made half as much while dealing with none of that, that might have been a worthwhile trade.

I think that also gets at different languages have to make different trade-offs there. Npm allows that, as do yarn, and Pnpm, and so on… But you generally are fine with that on the server, and extremely not fine with that on the client, because your bundle size ends up mattering enormously. And so you have to be able to pick different trade-offs. And the Npm etc. one is hard, because it’s trying to serve both a server ecosystem and a client ecosystem or browser ecosystem… And those just have fundamentally different needs.

One thing that Rust doesn’t do and Npm theoretically does, and Yarn and Pnpm actually do, is support the idea of peer dependencies, which is this secondary dimension to all of this where you want to be able to say “I want to support a range of versions of this dependency of mine, and I want to maybe even be able to test against them.” And in principle, that’s possible in the Npm ecosystem, and more or less nowhere else. I think it’s an under-invested area. If you’re building a new programming language, you should bake peer dependencies in as a first-class concept, because frameworks - React being the classic example, but in Rust, Tokyo is a very good example, similarly… Things that sit at that foundation level that everything else builds on - you really want to be able to say “My Library is currently built against the latest version, and Predrag’is is built against a version four minor versions back”, but both of us just say “We have a peer dependency on the same major”, and your resolver can now say “Ah, I’m going to get the version that satisfies both of these”, and neither of you ships a direct dependency on it; you both specify a peer dependency, and that can “just work.” And it’s a very powerful thing that mostly doesn’t exist… Again, Yarn and Pnpm finally have gotten this right in about the last two to three years. But it allows you to pick a different set of trade-offs, and to bring this all the way back around in a very full circle to “Does semver work?” It only works because you have that ability to communicate those sets of constraints, that these share the same major version… And they might differ on which features they’re using, but they share the same major, and so you can actually build tooling around that. And you can’t do that without something shaped like SemVer, even if we iterate on the details along the way.

Well, you answered my unspoken question, which was “Is there a better way?” And is it too late to unadopt SemVer?

[laughs]

[01:22:07.16] That was my unspoken question, and it’s kind of been camping out in that area, like “Is there a better way?” Because it’s fraught with a lot of issues, and a lot of challenges, but is there a better way? And I think you’ve kind of answered that.

I think the best place to take it from here is to invest in tooling that we have, and make it work in even more of the cases. Starting from scratch, we’re going to relearn a lot of the same very painful lessons on a much longer timescale, I think.

Right. Well, let’s leave folks with some waypoints, Predrag, to some of the tooling you’ve been building, perhaps Chris, how they can connect with you, to plug into this conversation and have a say… Because there are so many details and so many little avenues and ways that we can go with this. We could talk about it all day, but we may walk in circles… SemVer Checks… What did you call it? Create SemVer Checks…?

Cargo-semver-checks.

Cargo-semver-checks. It seems like maybe state of the art for people who want to build similar tooling for their ecosystem. We have to permeate all ecosystems with this tooling, so that everybody who’s doing SemVer can actually do SemVer as well as we possibly can. So maybe that’s a great place for people to either help out in Rust, or look at what you’re up to and emulate in their language of choice.

Absolutely. And the infrastructure that cargo-semver-checks is built on, the core piece is called Trustful. It is a weird take on a database query engine. That is a topic for a whole other podcast episode sometime.

Okay…

But the key thing there is that it’s language-agnostic. So it has bindings for Python, it works in JavaScript, you can run it in Wasm in the web browser, if you wanted to; you could call it from anything. And so you could use it as the foundation of a semver linter for another language. That’s in fact how my Python SemVer linter works as well. And I’m trying to talk Chris into building one for TypeScript.

[laughs] I already have too many projects…

Come on, Chris…

…but if somebody else wants to partner with us on it…

There you go. Then what? What should they do?

Then reach out.

Then reach out. Okay.

[laughs]

Yeah. So –

Finish that sentence.

Predrag, I’ll let you finish and then I’ll –

Yeah. And Cargo-semver-checks and Trustful are both completely open source, so if someone wants to come in, learn how to write some lints, check out a weird database, maybe write some database optimizations or whatever you’re interested in, very happy to do that. Like I’d mentioned earlier, I have worked with four college students who did some tremendous work, and honestly made Cargo-semver-checks into what it is today… So if four people who hadn’t even finished college yet could have such a spectacular impact on the ecosystem, then anyone listening to this podcast could absolutely have a huge impact. So come talk to me, I’m very happy to help.

Yeah. On my end, I will say, if you’re in the TypeScript community, do look at SemVer-ts.org. The other thing is I’m happy to help someone get up to speed on how you would translate that spec, which - it’s very long and very detailed, because it has to be. How you would translate that into a query rule set for something like Trustful, because I do think we could do it. And I think we could do it using a lot of existing infrastructure like TypeScript ESLint. I don’t think it would be a “boil the ocean from scratch” project.

But the other thing I’ll say is, if you want to think about “Hey, how would we do this for Elixir?”, especially as Elixir starts pulling in types, and makes a lot of these things even more tractable than it would already be today, I’d be very happy to talk to you about what a SemVer-Elixir.org kind of spec might look like. And I’d actually love to see these kinds of things emerge more generally, of what is it you need to think about. Cargo has a 10,000-word document, which you can read through, and gets added to on the regular.

[01:25:59.07] TypeScript now has one, though I’m basically the only maintainer; I would love other people to contribute there. And if we started building up that kind of ecosystem of projects for a variety of languages… What does it look like in C#? What does it look like in Java? And started making that more of a norm. What does it look like in Ruby? That can also get a flywheel going. Because someone doing the time to think through that, and then a bunch of other people taking the time to codify that into something like the Trustful rules engine, build a schema, and ship those kinds of things, we could make this a norm.

Maybe we’re having a five a conversation five years from now, where tons of languages do this, and it’s just normal. That would be amazing. And it’s just time and effort. And that’s a big “just” on that statement, but it is a “just”. These things are actually tractable, solvable problems. So come talk to me. You can find me at ChrisKrycho.com, and email and whatnot is all there. I’m on – I’m really easy. I’m @ChrisKrycho everywhere, because it’s a globally unique identifier. There’s only one of me on the planet.

Nice…!

Is that right? Good for you.

Easy to find.

My blog is predr.ag, because .ag is a TLD, and it’s an uncommon enough name that –

Oh, he’s a TLD hacker. I love it.

That’s cool.

Well, you guys make sure we get links to all of those things. I’ve got Hyrum’s Law, we have some other stuff that we’ll throw into the show notes so everybody can just click through, versus having to type and memorize and whatnot. Very cool. Adam, anything else?

My thought after this conversation is that there should be some sort of like consortium or working group or just like collective for SemVer…

People who care.

Right. Because you’ve got skin in the game, you do care, and you’ve got expertise that applies. And so just as you said, if you want to help somebody else in a different ecosystem, you’re willing to help them out. I feel like there needs to be like a little group of you all folks who just care enough to cross-pollinate, so that the whole entire ecosystem of software gets better. Because it’s not about camps. That’s why this show is called the Changelog and not the Rust show, or the Ruby Show, or the whatever show, because we care more importantly about software at large, the intersection of software and business, and all the fun things you know about this show. That’s why we went that route, because we cared about software at large, open source at large, and just really the direction we can all go. The culture of software development. And so I would encourage you guys to consider - if it doesn’t exist, make it exist. And if you do –

SemVer nerds.

There you go. I like that, Jerod.

Come join the SemVer nerds community.

Is there a .nerds TLD? Because then we could have semver.nerds. That would be the best.

There needs to be.

There should be.

Yeah. Predrag will figure something out… Awesome. Thanks, guys.

Yeah, thank you so much. It’s been awesome.

My pleasure. Yeah. Thanks for having us.

Thank you for having us.

Changelog

Our transcripts are open source on GitHub. Improvements are welcome. 💚

Player art
  0:00 / 0:00