Nitpick: Rust doesn't panic in this situation, it saturates (i.e., returns the largest possible value for the given integer type). Among other reasons, because a lot of existing programs that triggered this UB happened to work in practice, and would have experienced the panicking behavior as a severe regression.
This case also shows the limits of Rust's safety culture; they knew about the problem for a long time, and could have fixed it right away if they'd been willing to make programs that do a lot of float-to-int casts eat a performance regression, but a number of users objected strongly to this. So it remained unfixed until they figured out a way to make it fast enough that no one would really notice.
> Rust doesn't panic in this situation, it saturates
Yes sorry, in my head I'm thinking about what I'd want here because I do not like any of Rust's 'as' casts and in fact the thing I'd want here (TryInto) just does not exist on purpose for this reason. I wonder if I've ever run into this, realised I can't write a TryInto and if so what did I end up doing - interesting.
It's actually quite straightforward to write a TryFrom impl that returns Err if the conversion fails (i.e., if the input floating-point value is infinite, NaN, or out of range of the target integer type). The reason this hasn't been done is, what if the input is within range but has a nonzero fractional part? Do you truncate or round (meaning the conversion can sometimes be lossy), or do you return Err unless the conversion can be done losslessly? It's not obvious which behavior is right, but they'd have to pick one.
Sure. These decisions are tricky, I had this concern in `realistic` too.
My `Rational` type is the "big rationals" (the finite subset of rational numbers I can represent with your available RAM) and these are certainly able to precisely represent any 32-bit or 64-bit float which isn't NaN or an infinity. However, vice versa is not true of course. 0.1 is a very easy Rational, but of course binary floating point cannot represent this exactly.
In the end I punted, TryInto<Rational> is implemented for f32 and f64 but the opposite is not provided at all.
It's interesting to consider that the same question would've surely come up during C and C++ language development, but that the answer is informed by the impact on the ecosystem, which was dramatically different when C, C++, and Rust were all being developed.
I don't think it would "surely come up". The C++ community is very comfortable with "Don't do that" as the lesson even though it's not actionable.
Like I said, safety is cultural. You can invent this stuff, somebody did, but the way most people end up doing it isn't because they all spontaneously invented the same solution, it was absorbed from their culture. C++ culture says "Don't do that" all the time instinctively. An actual concrete objection to fixing it might be offered if you insist on one, but they start with "Don't do that".
Sorting is my go-to example. Rust's sorts are safe. If I sort "Alligator", "Baboon", "Cat", "Donkey" then no matter what my ordering rule was nothing crazy happens. If my rule was nonsense, like "Every item is before every other item" then Rust might panic, it is allowed to do that - but otherwise it just will give me back the same four items but who knows what order because my ordering rule is nonsense. That seems easy enough, right?
In C++ if my ordering rule does not meet the precise requirements of the C++ language then all bets are off, that's Undefined Behaviour. I might get back "Cat", "Cat", "Cat", "Cat" even though there was originally only a single cat, or it might scribble past the end of my list of animals, crash, or anything at all. And while it would be permissible for real C++ standard library implementations to do better, on the whole they don't. "Don't do that" is the answer if you ask why this defect is allowed.
My sense of sentiment in the project right now is that people want to keep the invariant that assignment (to an ordinary memory location) is always just a memcpy and never calls arbitrary user-defined code, because if that code does something unexpected, debugging (at least if a human's doing it) is likely to be hampered by the syntactic invisibility of the call site. This is why the most obvious ergonomic-reference-counting proposal (have a trait that lets types opt into implicit clones) ran aground, and they're now experimenting with (conceptually clunkier, in my opinion) alternatives that aim to make reference counting ergonomic but still syntactically visible.
So if Rust ever gains move constructors, you'll probably have to call them explicitly, the way you have to explicitly call .clone(). (There's actually already a third-party library that does something like this (https://docs.rs/moveit), and I think Crubit is using a similar API to let Rust code call C++ move constructors.)
The people working on this are aware that it poses backcompat problems that don't have obvious solutions. They are looking into non-obvious solutions. https://lcnr.de/blog/2025/11/28/implicit-auto-traits-assoc-t... is the most up-to-date one I'm currently aware of.
Niko Matsakis (T-Lang) has since also published a post on only-bounds: https://smallcultfollowing.com/babysteps/blog/2026/06/09/onl.... Sized hierarchy, Move, and Forget/Leak all share the same fundamental compat issues, so we're all pretty motivated to solve the underlying problems in a way that enables all the other features to be built on top.
I had read that post, but IIUC it doesn't address the kind of backcompat issues that I meant to be referring to (the ones that would apply even if there was only ever going to be one new question-mark trait).
This is not currently a project goal. As with many things in the Rust project, it could be, if someone wanted to step up and dedicate the required engineering resources.
Out of curiosity, is there some reason it would be harder than I'm imagining? It seems like a fairly straightforward type-system feature. (I.e., it would be a lot of work, but only because any significant new language feature is a lot of work.)
The problem with the trivial implementation is that it's barely useful. You cannot use indexing, for example.
Not to mention it's still not easy: to have any usefulness, there will need to be a way to be generic over it (similar to keyword generics), and that's far from trivial.
Very similar to Windows Structured Exception Handling, which the Win32 implementation used, and also looks similar to Java, but without checked exceptions. try/except/end and try/finally/end blocks.
The major differences with C++:
* objects are references not values, cutting out all the copy constructor,
assignment operator, destruction on out of scope etc.
* objects are zero-initialized after allocation and before constructors run
* constructors are run from most derived to least derived
* calling Free method checks if Self is nil
* this + zero init means that you can call '.Free' on all the objects you
reference in the destructor without checking if they're nil first,
handling partial construction
It's not a memory safe language, but it does give you a bunch of idioms that, if you stick to them, you don't feel nearly as much pain as C++.
Not all objects are references, because Delphi still supports the Turbo Pascal object model for compatibility, regardless of how many years they are deprecated now.
Agree with the rest, I always loved how Borland picked Object Pascal extensions from Apple, merged them with what was going on with Modula languages and C++ during the 1990's, while coming out with something saner.
While at the same time keeping C++ around, as market value when buying the whole package.
What everyone is going crazy regarding Zig, Odin, Jai, C3, whatever improvements over C and C++, where already available in Delphi, Modula-2, Ada and co, with Delphi being the most affordable option until Borland decided to pivot to big corp.
They recognize that there's going to have to be some kind of compatibility or migration story for existing APIs based on Pin, but have decided to punt that question to next year. For now, the focus is on the non-async-related use cases for these new language features (for which Pin is already insufficient); once those are known to work, then they'll shift focus to letting the async ecosystem benefit too.
If anything, pin ergonomics is at greater risk. People still discuss if we need both, but if we don't, then we go for this goal and not pin ergonomics, as it's more general.
It (the immobile types proposal) is at a greater risk because it is a very pervasive and complicated change, and the expected semantics are not fully understood. It might be that there is no way to do this well.
AGPL software is in practice always gratis, because anyone who has a copy can post it on the internet where anyone can download it. The monetization benefit of AGPL is that it makes it significantly more annoying (but by no means impossible) for IaaS providers to sell managed hosting for the software without making a separate commercial agreement with the copyright holder. This usually isn't relevant to dev tools that users run on their own machines.
You say this as a fact, but you're wrong and I'm living proof of that. You can earn good income selling copies of AGPL software. It doesn't matter if people upload it to a public place, that's their right, and *that right* is why so many people buy a copy in the first place. You are spreading FUD.
This case also shows the limits of Rust's safety culture; they knew about the problem for a long time, and could have fixed it right away if they'd been willing to make programs that do a lot of float-to-int casts eat a performance regression, but a number of users objected strongly to this. So it remained unfixed until they figured out a way to make it fast enough that no one would really notice.
reply