57 comments

[ 5.8 ms ] story [ 131 ms ] thread
(comment deleted)
I’d love someone to comment on this who’s knowledgeable. I know we have a few rust experts in HN. I’m curious if this is a bug with the update. My novice brain tells me this shouldn’t have happened.
So, one area in which Rust is okay with "breaking" source compatibility is if some inference gets confused, and you need to add a type annotation.

This issue was discovered before the release, and a new version of time was released to fix it.

  cargo update -p time
and you're done.

This doesn't happen very often. It bit one of my projects too, and it took me longer to realize that it was a new version release that caused the problem than it did to fix the problem. It's been so long since something like this happened to me I cannot remember when it happened last.

Here's the upstream issue, if you're curious https://github.com/time-rs/time/issues/693

I was wondering why and it looks like a new trait implementation in std made a type placeholder ambiguous in older versions of time?

  time was caught in a crater run when a new trait implementation was added to the standard library. While it is a breaking change, it is considered acceptable breakage because the alternative is never adding any trait implementations or methods.
https://github.com/time-rs/time/issues/681#issuecomment-2143...
I wish rustc had a way to 'pin' the output of type inference, in the same way that Cargo.lock pins the versions of dependencies. That way, old code would continue to compile based on what the types were originally inferred as, even if the code retroactively became ambiguous.

This wouldn't be a perfect solution. Due to conditional compilation, any given variable in the source code might not exist on certain platforms, or it might even have a different type depending on the platform or configuration. So the question of "what type does this variable have" doesn't always have a single answer to record in a lockfile. And macros make things even harder.

But even as an imperfect solution, I think it still has the potential to avert most real-world cases of breakage caused by Rust upgrades (and even dependency upgrades). As a bonus, the same mechanism could potentially also be used to detect breaking API changes.

I think the closest to what you're asking for is to build with a contemporary rustc version (have a rust-toolchain.toml in your repo). This isn't a satisfactory answer, but it is an available option nonetheless.
I wish rustc had a way to 'pin' the output of type inference

Would that not be "take the outcome of said type inference, write it into the LHS as a type declaration or into the RHS as a generic parameter", aka make the inferred type permanently declared? We're not totally powerless for addressing the practical implication here, and I don't know how we could have stable yet still implicit type inference across compiler versions unless that type inference logic never changed ever again.

sounds like they should have added a warning
Whenever I see this message in a repo:

> An owner of this repository has limited the ability to comment to users that have contributed to this repository in the past.

I immediately know it should never be used. time-rs is such a repo. They don't want you or need you there, and they don't care about your bug reports or even your questions.

This shows a stark lack of empathy for maintainers. Per a maintainer’s Mastodon profile, the comment lock is temporary and specifically in response to driveby commenters dogpiling a bug report where the issue in question has a working fix that was already released months ago. Said abusive comment traffic probably often originated from blog posts and comment threads similar to this one.
That hardly justifies the block. Users deserve empathy too, considering they have to bear the consequences if things go wrong. One now doesn't even know and cannot even estimate the number of other issues that must have gone unreported. It's not safe or wise to use a package that is so shrouded in mystery. It is in fact foolhardy.

If maintainers don't want to address issues, they only have to ignore them. At least other users can then still discuss the issues in-between themselves. A lack of empathy for maintainers does not come into the picture.

No one should have to go to Mastodon or wherever to find out about the author's stance. The readme of the repo is where the stance belongs. The readme is currently grossly misleading, asking users to use Discussions, but this is not possible.

Overall it seems like a hazardous project to use.

This is an open source tool meaning that maintainers can communicate however they like. If you don’t like it, you can fork it and do things your own way. But there is absolutely nothing wrong with temporarily disabling comments. If anything, that’s a sign of self care and this wisdom.

A little more empathy wouldn’t hurt you.

I have plenty of empathy; it is just not biased the way you want it to be. As it stands, the software has a misleading and lying readme, asking to use Discussions when this is not even possible. A little broader perspective won't hurt you.
When it comes to open source maintainers, my perspective is really simple.

They are volunteers who spend their free time so that I can build cool things for zero dollars. My demands are proportional to the terms ‘volunteers’, ‘free time’ and ‘zero dollars’.

But if you’re this passionate about communications in open source projects, I know you could find hundreds of projects that need help with documentation and overall communications. Use the passion and good things will come.

So you think they can push whatever nonsense and liabilities they want upon users? No, they cannot. By offering a software, as advertised in its readme, they're in effect making a particular promise. They do not get an automatic right to violate this promise. If they cannot maintain the software, especially when it comes to major bugs, the repo should be set to Archived. In the case of this repo, since user interaction is disabled, it is even impossible to know which other major bugs may exist as would've otherwise been reported by users. Being a volunteer is no excuse to be horrible.
No maintainer is obligated to maintain access to a discussion space for their users.

> One now doesn't even know and cannot even estimate the number of other issues that must have gone unreported. It's not safe or wise to use a package that is so shrouded in mystery. It is in fact foolhardy.

Issues don't get reported for any number of reasons. All open source is use at your own risk.

Do not try to equalize the risk of using an open software where issues are allowed versus one where issues are not allowed. The risks are not the same. In blocking users from commenting, it is obvious that the authors of this software are trying to cover up for gross negligence and bugs.
Do you simultaneously believe "it is a maintainer's prerogative to ignore any public feedback they receive":

If maintainers don't want to address issues, they only have to ignore them.

and that disallowing comments on a temporary basis, starting in the last few days, is gross negligence?

And do you also believe that the offensive part of this scenario is that their users, who may or may not be compensating them but statistically are not, temporarily don't have a public venue in which to chastise them for a problem that was already responsibly solved in a previously released update?

Do not try to equalize a maintainer guarding their time and energy from having to deal with an issue that has already been fixed and users that refuse to search or read with trying to cover up for gross negligence and bugs.
There are better ways of dealing with it. For example, a particular issue can both be locked and pinned. The readme too can be updated to reflect the concerns. There is never a need to disable all user interaction. It is foul-play and it stinks of other major bugs that the authors don't want users to become aware of.
I work in a large code base with 1800+ crate dependencies (don't ask) with half of them being unstable crates not really published to crates.io. And yeah, we run into this issue a lot. The recent one was caused[0] by human error.

It's usually human error but there's also a lot of cases due to Cargo's choice to go to pick the highest version that satisfies the constraints. It's a reasonable choice on Cargo's part but yes, our team has learned to be very conservative to diffs on `Cargo.lock` and most of our dependencies are pinned.

[0]: https://github.com/denoland/deno_core/issues/797

This is still a pretty astounding breakage possibility let alone that it actually broke.

Such inference seems inherently brittle and inevitably will break.

This sort of thing certainly does Rust no favours with regards to adoption.

Typically this is solved by recording a "minimum" version supported by the dependency (e.g. in the dependency) and then accommodating by behaving differently. For example, by gating the change as an opt-in to a newer version. Instead everyone is forced to trip over this. What a disaster.

> Such inference seems inherently brittle and inevitably will break.

This is just one data point, but again, in 80 releases, I can't remember the last one that did this to me. Obviously that's not a universal experience, as one of your siblings seems to run into it more than me.

> Typically this is solved by recording a "minimum" version supported by the dependency

The issue is the opposite. The dependency needs to be updated to fix the issue, not held back.

> What a disaster.

From "wait why did my CI pass yesterday but fail today" to pushing a commit with the fix, it was like ten minutes. Five of that was realizing that the breakage was because of a new Rust release. This happens so rarely to me that I didn't even think of it at first.

Alternatively, don't update to the latest Rust. If the Rust version was pinned, this bug wouldn't manifest either.

The fact that you can just `cargo update` is besides the point. The standard library should never make a breaking change that breaks running code. Why is this the exception? If the problem is that it wouldn't be possible to add new traits, then solve that by gating access to new traits on a minimum version.

It seems like the change should have been made as part of a new edition. This PR seems relevant: https://github.com/rust-lang/std-dev-guide/pull/66

edit: I can't help but feel like we're moving the goal posts here. "Just don't update Rust" was the sort of response you'd encounter with nightly when nightly was basically required to do anything serious, but this is stable. There is even an accommodation for such changes! Why wasn't it used?

This all just reeks of laziness and disregard for users.

> The standard library should never make a breaking change that breaks running code. Why is this the exception?

It is an exception because it is a very minor breakage that is trivial to fix. Sometimes this is worth it. Sometimes it is not. I guess they made the call that it was worth it. As a user who was affected, I don't mind that this happened.

> If the problem is that it wouldn't be possible to add new traits, then solve that by gating access to new traits on a minimum version.

The issue here is rustc being updated. If the new trait was only available on the new version, the same thing would have happened. I'm not sure this suggestion would fix things.

> This all just reeks of laziness and disregard for users.

I have many criticisms of the Rust project these days, but I truly believe you are blowing this far out of proportion.

I hate to drag Go into this but I can't help but compare what happened here to the attitude here: https://github.com/golang/go/discussions/55092

My initial impression was that this is boiling the ocean but there's substantial foresight.

If I'm blowing this out of proportion then the bar is already too close to the ground for my liking. This is solvable.

I don't remember the last time a tool chain update broke my build. It's one thing if it's a bug fix. But for practically everyone to have to update?

If 100,000 developers have to trip over this, say, then with your 10 minute estimate that equates to a million minutes, nearly 8 years at 40-hour weeks.

This appears to be a problem totally worth solving.

> It is an exception because it is a very minor breakage that is trivial to fix.

Or so it seems. But who cares about quality ? /s

> This PR seems relevant...

It's not. The change doesn't have anything to do with the prelude.

It was a new conversion was added, where existing code made an assumption that there was only one possible conversion that satisfied an inferred type. (In fact when that code was written it was superfluous as the conversion was actually converting from one type to the same type). That code was removed in https://github.com/time-rs/time/pull/671

The only code that is strongly broken by this is code that doesn't update its dependencies more regularly than its compiler... locking your deps but refusing to do so with your toolchain is just plain weird IMHO.

[flagged]
JS from 15yrs ago definitely work today. Navigate to a webpage that hasn’t been updated for 15 years and see for yourself
Maybe I phrased that poorly but what I was referring to is the library ecosystem behind stuff like React. I highly doubt a React codebase from 2015 would build today.
(comment deleted)
> When I did that, despite having not changed any dependencies, I got a build error in CI/CD.

Isn't this resolved by passing `--locked` to `cargo`? If you don't want it to resolve deps, don't let it. This isn't bit rot.

This lament seems similar to "I can't compile this Rust code (using an older version of rustc)".

The issue is the opposite of --locked: the older version of the dependency was the one causing the issue. To fix it, you need to update the dependency, not keep the one in the lockfile.

Also, in this case specifically, --locked would be a no-op. Rust doesn't automatically upgrade dependencies unless you either change your Cargo.toml, or run cargo update to change your Cargo.lock. The author didn't change any dependencies, none of their versions changed, and so --locked wouldn't have changed that the failure happened.

> The issue is the opposite of --locked

Shows what I know!

So -- it's type inference of a new rustc and an old dep? That's much more interesting.

Yes, this is Rust 1.80.0 not being able to compile code that worked with 1.79.0. The vast, vast majority of the time, you don't need to worry about that, but every once in a while, something like this happens. There are limited times when it's permissible, and often steps are taken to limit the blast radius. The only reason people are noticing this is because time is such a popular dependency. And the fact that it was noticed and fixed before release is a success story for that release process, as users didn't have to wait for a fix, and could get their code fixed immediately.
I am not sure any of what you described can be declared a success in any sense.

Failure #1: The language deployed a non-backward compatible change. Failure #1b: and their ecosystem thinks that's OK.

Failure #2: users only saw the need to upgrade the dependency because the language broke that dependency first. So unless they had some other reason to update the given dependency ahead of the compiler upgrade, they would be hit by the problem no matter what.

Failure #3: downplaying The importance of the breakage by implying the blast radius would've been smaller if the victim of the first two failures was a lesser known dependency.

I can't see any success - of the release process or in any other way.

And finally, framing a failure as success is one central problem I have with Rust ecosystem's toxic positivity. In any other healthy community the answer would be "OK, we made a mistake, let's run a post-mortem and do better", or even plain embarrassment - but Rust's ecosystem is the only one in which there's a failure in the joint of two releases processes and claim it as a "success story for [one of the] release process".

My hope, as bigger games adopt Rust (US Gov, MS, Linux etc) these attitudes will change.

> My hope, as bigger games adopt Rust (US Gov, MS, Linux etc) these attitudes will change.

It is going on since some years with no change in sight.

You can make an abstract argument as much as you'd like, but Rust toolchain upgrades cause the least churn of any ecosystem I've worked with in the past 25 years of being a software developer. That includes C projects.

I've spent more time writing Hacker News comments about this than I did dealing with it.

> Failure #1: The language deployed a non-backward compatible change.

I guess I'm trying to understand how this is particular to Rust. Isn't this something that can happen in any language which uses type inference? I wonder how other languages resolve. It may be fair to say -- "if you rely upon type inference, the algo isn't guaranteed not to change" as a caveat on backwards compatibility.

> And finally, framing a failure as success is one central problem I have with Rust ecosystem's toxic positivity.

Yeesh. The rending of garments is just too much.

> The vast, vast majority of the time, you don't need to worry about that, but every once in a while, something like this happens

Oof in Julia this never happens.

Sounds to me that rust isn't ready for production.

Backward compatibility (in terms of successful compilation) is never absolute. It's just Rust makes it very rare, and usually justifiable (say, fixing sound issue), while still allows making good progress. In this case, it's an inference change, which is easily fixable. Inference breakage is explicitly allowed in Rust compiler policy. You won't get any new methods in standard library if you want absolute backward compatibility.
I think that in an ecosystem that's more standardized like C or C++, you will have better backwards compatibility because compilers are written to the standard rather than the other way around.

I look forward to the time when rust gets a GCC-based compiler, as it will force the language to become more standardized. I think that rust will eventually ossify. But for now it is good to have some time to learn the language before it's widely relied upon, and to for the language to make reverse-incompatible changes and improve.

I think the whole benefit of rust in the first place is that it is able to improve upon C/C++'s design while being able to make reverse-incompatible changes with hindsight.

In my (too long) experience of C and C++ this is absolutely not the case. New compiler versions are much more frequent than new language standards and frequently will barf on existing code when you make an upgrade. Usually when it happens it will be because of some unsafe/undefined/broken edge case that noone thought of before so some (evil) developer relied on it and then the subsequent compiler writer figured out how to detect and warn/error on.

The whole premise of the article seems weird to me. "New compiler detects problem in old code" doesn't seem like a bug to me especially if the breakage is caused by type inference. If you want the ci/cd to not change in any way then you need to build in an environment which doesn't update in any way.

>If you want the ci/cd to not change in any way then you need to build in an environment which doesn't update in any way.

Necroing this, but what would it take to ensure forward-compatibility of rust like this?

(comment deleted)
(comment deleted)
(Rust playground maintainer here) Incidentally, I also updated the Rust playground today. My usual process is to update everything [0] that is SemVer compatible and then one-by-one update packages with SemVer incompatible version bumps.

During the upgrade, I was thinking to myself how generally I’m fairly blasé about these updates — I don’t even attempt to build the code with the SemVer compatible updates because it’s so rare for a crate maintainer to stuff up such an update. The SemVer incompatible updates get a build just as a sanity check.

Contrast this to the JS dependencies where an upgrade tends to instill more dread in me. Part of this is a lack of confidence in my test suite, part of it is that it’s the more UI heavy component which is hard to write tests for in the first place, but a non-zero part is my lack of trust in the JS libraries and tooling. I wonder how much of that is still true today versus how much I am once bitten, twice shy.

[0]: https://github.com/rust-lang/rust-playground/pull/1079

Article is just whining! Author didn't investigate what caused an issue.
To clarify this a bit:

1. The widely used Time crate had some code which converted to an unspecified type. The code then converted this again in a following line. Because there was only a single possible (identity) conversion, this code was a noop. Regardless this was fixed in time@0.1.35 (released in April 9, 2024). [1]

2. Rust 1.80 (released July 27, 2024) introduced a new trait implementations that made the second conversion now cause the first conversions to have two possible inferences. [2]

3. Rust packages that upgraded the compiler to 1.80.0, but used a version of the time crate prior to 0.1.35 fail to build the time dependency. This is mitigated by the fact that 0.1.35 is a semver compatible update, which just requires running cargo update to use.

[1]: https://github.com/time-rs/time/pull/671

[2]: https://blog.rust-lang.org/2024/07/25/Rust-1.80.0.html#stabi...

Something that would be neat to see from Rust here would be a way to measure the impact of these sorts of compiler and dependency bumps in order to reduce the blast radius of this sort of thing. Ideally being able to say "this change requires X devs to do process which cost Y minutes, so it's worth investing Z minutes to avoid / document it". I often want to make public API changes to a library I maintain, but the best tool I have for understanding who those changes will affect is often a GitHub search for code.

> Something that would be neat to see from Rust here would be a way to measure the impact of these sorts of compiler and dependency bumps in order to reduce the blast radius of this sort of thing. Ideally being able to say "this change requires X devs to do process which cost Y minutes, so it's worth investing Z minutes to avoid / document it". I often want to make public API changes to a library I maintain, but the best tool I have for understanding who those changes will affect is often a GitHub search for code.

They have such a tool, its called crater. It was used used in making the decision to delay merging the new trait impl until the new version of time was published and in wide use.

https://rustc-dev-guide.rust-lang.org/tests/crater.html

>> I often want to make public API changes to a library I maintain

> They have such a tool, its called crater

There are tools out in the wild that help crate authors do similar things. For example, I wrote my own[0] to evaluate some breaking changes for SNAFU. There's also crater-at-home[1] which I don't have any direct experience with.

[0]: https://github.com/shepmaster/mini-crater

[1]: https://github.com/saethlin/crater-at-home

There's of course also cargo semver, but I don't think it would have helped here.
That's exactly what I was hoping would exist (thanks for posting this). I was aware of crater, but not aware of these tools.
To expand on some of the things already said:

This issue was caught during a crater run. An earlier version of time had (accidentally) an "identity" conversion (<T as Into>::into(T)), which was caught by a blanket trait implementation. That call shouldn't have been in the library in the first place (it was speculated by its author that it was used in some transitory test in the past). Inference is brittle, there's no way around that. Adding a new trait impl can break down streams. The project makes an effort to avoid big blast radius when this happens, including backing off on the change and/or letting the ecosystem adapt before actually introducing the change. In this case there was a decision made to bite the bullet given that time would have a fix. Personally I would have preferred to delay the landing of the impl in order to add a migration lint for at least one release cycle. Today you can pin your rustc version to avoid these kind of surprises, but you shouldn't have to. I am personally concerned about being able to build random projects from today in 10 years, and I hope we can manage that. Looking at the root problem here, I'm gonna see if I can write a lint against the identity conversion impl in expressions to see how many more potential mines are in the ecosystem. If there aren't that many, we can enable that lint by default and prevent future occurrences of this. I would also like to extend the diagnostic people hit in this case to be more actionable (but end users wouldn't see that until 1.82 at the latest when compiling against an older time, it might be worth it for future users only). Completely freezing std to completely avoid these issues is not reasonable. End users can always smoke test against beta in order to get advance notice of these kind of issues. These kind of issues should be rare. If they are not, it's a fuck-up, and we'd love to see bug reports so that we can rework our release process accordingly.