Incidentally, I think this is one of Rust's best features, and I sorely miss it in Python, JS and other languages. They keep me guessing whether a function will mutate the parent structure, or a local copy in those languages!
Incidentally, I recently posted in another thread here how I just discovered the 'named loop/scope feature, and how I thought it was great, but took a while to discover. A reply was along the effect of "That's not new; it's a common feature". Maybe I don't really know rust, but a dialect of it...
1. Always keep the language reference with you. It's absolutely not a replacement for a good introductory textbook. But it's an unusually effective resource for anybody who has crossed that milestone. It's very effective in spontaneously uncovering new language features and in refining your understanding of the language semantics.
What we need to do with it is to refer it occasionally for even constructs that you're familiar with - for loops, for example. I wish that it was available as auto popups in code editors.
2. Use clippy, the linter. I don't have much to add here. Your code will work without it. But for some reason, clippy is an impeccable tutor into idiomatic Rust coding. And you get the advantage of the fact that it stays in sync with the latest language features. So it's yet another way to keep yourself automatically updated with the language features.
Rust has an unusually short release cycle, but each release tends to have fewer things in it. So that is probably about the same when it comes to new features per year in Python or C++.
But sure, C moves slower (and is smaller to begin with). If that is what you want to compare against. But all the languages I work with on a daily basis (C++, Python and Rust) are sprawling.
I don't have enough experience to speak about other languages in depth, but as I understand it Haskell for example has a lot of extensions. And the typescript/node ecosystem seems to move crazy fast and require a ton of different moving pieces to get anything done (especially when it comes to the build system with bundlers, minifiers and what not).
Many people encounter these algorithms after many other people have written large libraries and codebases. It’s much easier to slightly extend the language than start over or (if possible) implement the algorithm in an ugly way that uses existing features. But enough extensions (and glue to handle when they overlap) and even a language which was initially designed to be simple, is no longer.
e.g., Go used to be much simpler. But in particular, lack of generics kept coming up as a pain point in many projects. Now Go has generics, but arguably isn’t simple anymore.
Even adding a new keyword will break some code out there that used that as a variable name or something. Perfect backward compatibility means you can never improve anything, ever, lest it causes someone a nonzero amount of porting effort.
What programming language(s) satisfy this criteria, if any?
Thry do reserve the right to do breaking changes for security fixes, soundness fixes and inference changes (i.e. you may need to add an explicit type that was previously inferred but is now ambiguous). These are quite rare and usually quite small.
On the other hand, I did find what I think are the relevant docs [0] while looking more into things, so I got to learn something!
[0]: https://docs.adacore.com/gnat_rm-docs/html/gnat_rm/gnat_rm/c...
I can't think of any established language that doesn't fit that exact criteria.
The last major language breakage I'm aware of was either the .Net 2 to 3 or Python 2 to 3 changes (not sure which came first). Otherwise, pretty much every language that makes a break will make it in a small fashion that's well documented.
PHP has had breaking changes [1].
Ruby has had breaking changes [2] (at the very least under "Compatibility issues")
Not entirely sure whether this counts, but ECMAScript has had breaking changes [3].
[0]: https://go.dev/blog/loopvar-preview
[1]: https://www.php.net/manual/en/migration80.incompatible.php
[2]: https://www.ruby-lang.org/en/news/2025/12/25/ruby-4-0-0-rele...
[3]: https://tc39.es/ecma262/2025/#sec-additions-and-changes-that...
C# for instance isn't such a "small language", it has grown, but code from older versions, that does not use the newer features will almost always compile and work as before.
breaking changes are for corner cases, e.g. https://github.com/dotnet/roslyn/blob/main/docs/compilers/CS...
Java is very good here, but (and not totally it's fault) it did expose internal APIs to the userbase which have caused a decent amount of heartburn. If your old codebase has a route to `sun.misc.unsafe` then you'll have more of a headache making an upgrade.
Anyone that's been around for a while and dealt with the 8->9 transition has been bit here. 11->17 wasn't without a few hiccups. 17->21 and 21->25 have been uneventful.
> I doubt that anybody truly knows <language>.
> Always keep the language reference with you.
> Use <tool>, the linter.
seem like they apply to all languages (and I agree that they're great advice!).
I assume I'm the one who taught you this, and for the edification of others, you can do labeled break not only in Rust, but also C#, Java, and JavaScript. An even more powerful version of function-local labels and break/continue/goto is available in Go (yes, in Go!), and a yet more powerful version is in C and C++.
The point being, the existence of obscure features does not a large or complex language make, unless you're willing to call Go a large and complex language. By this metric, anyone who's never used a goto in Go is using a dialect of Go, which would be silly; just because you've never had cause to use a feature of a language does not a dialect make.
Zig might be an option in the future, and it does give more control over allocations. I don't know what the exception story is there, and it isn't memory safe and doesn't have RAII so I'm not that interested myself at this point.
I guess Ada could be an option too, but I don't know nearly enough about it to say much.
i never got this point. whats stopping me from writing a function like this in zig?
fn very_bad_func() !i32 {
var GPA = std.heap.GeneralPurposeAllocator(.{}){};
var gpa = GPA.allocator();
var s = try gpa.alloc(i32, 1000);
s[0] = 7;
return s[0];
}
the only thing explicit about zig approach is having ready-to-use allocator definitons in the std library. if you excluded std library and write your own allocators, you could have an even better api in rust compared to zig thanks to actual shared behaviour features (traits).
explicit allocation is a library feature, not a language feature.It's a shame that you can't quite do this with a lint, because they can't recurse to check the definitions of functions you call. That would seem to me to be ideal, maintain it as an application-level discipline so as not to complicate the base language, but automate it.
Typically no... which is another way of saying occasionally yes.
> What would you do if you needed to call `unreachable!()`?
Probably one of e.g.:
unsafe { core::hint::unreachable_unchecked() }
loop {}
Which are of course the wrong habits to form! (More seriously: in the contexts where such no-panic colors become useful, it's because you need to not call `unreachable!()`.)> It's a shame that you can't quite do this with a lint, because they can't recurse to check the definitions of functions you call. That would seem to me to be ideal, maintain it as an application-level discipline so as not to complicate the base language, but automate it.
Indeed. You can mark a crate e.g. #![deny(clippy::panic)] and isolate that way, but it's not quite the rock solid guarantees Rust typically spoils us with.
I don't know 100% for sure. It's a bit confusing...
> What’s with all these new reference types? > All of these are speculative ideas
makes it pretty clear to me that they are indeed not yet part of Rust but instead something people have been thinking about adding. The rest of the post discusses how these would work if they were implemented.
I have seen &pin being proposed recently [1], first time I'm seeing the others.
[1] https://blog.rust-lang.org/2025/11/19/project-goals-update-o...
Python at least is very clear about this ... everything, lists, class instances, dicts, tuples, strings, ints, floats ... are all passed by object reference. (Of course it's not relevant for tuples and scalars, which are immutable.)
answer = frobnicate(foo)
Will frobnicate destroy foo or not?The function could mutate foo to be empty, if foo is mutable, but it can’t make it not exist.
No mention of references!
I don't care about references to foo. I don't care about facades to foo. I don't care about decorators of foo. I don't care about memory segments of foo.
"Did someone eat my lunch in the work fridge?"
"Well at least you wrote your name in permanent marker on your lunchbox, so that should help narrow it down"
1. Why isn’t there a variant of &mut that doesn’t allow swapping the value? I feel like it ought to be possible to lend out permission to mutate some object but not to replace it. Pinning the object works, but that’s rather extreme.
2. Would it be safe to lend the reference type above to a pinned object? After all, if a function promises to return with the passed-in parameter intact in its original location and not to swap it with a different value/place, then its address must stay intact.
3. Why is pinning a weird sticky property of a reference? Shouldn’t non-movability of an object be a property of the object’s type? Is it just a historical artifact that it works the way it does or is this behavior actually desirable?
4. Wouldn’t it be cool if there was a reference type that gave no permissions at all but still guaranteed that the referred-to object would continue to exist? It might make more sense to use with RefCell-like objects than plain &. This new reference type could exist concurrently with &mut.
This is a very insightful observation, and Niko Matsakis (leading influence of Rust's borrow checker) would likely agree with you that this is an instance where Rust's default borrowing rules are probably too permissive, in the sense that being more restrictive by default regarding the "swappability" of &mut could lead to Rust being able to provide more interesting static guarantees. See his blog post here: https://smallcultfollowing.com/babysteps/blog/2024/09/26/ove...
> Why is pinning a weird sticky property of a reference? Shouldn’t non-movability of an object be a property of the object’s type?
See this blog post from withoutboats: https://without.boats/blog/pinned-places/ for arguments as to why pinning is properly modeled as a property of a place rather than a type (particularly the section "Comparison to immovable types"), as well as this post from Niko that ties this point in with the above point regarding swappability: https://smallcultfollowing.com/babysteps/blog/2024/10/14/ove...
For 3, some objects only need to be pinned under certain circumstances, e.g. futures only need to be pinned after they're polled for the first time, but not before. So it's convenient to separate the pinnability property to allow them to be moved freely beforehand.
I don't quite understand the usecase you have in mind for 4.
Privacy. If an object has fields I can’t access, but I have an &mut reference, I can indirectly modify them by swapping the object.
More generally, there are a handful of special-seeming things one can do to an object: dropping it, swapping it, forgetting it, and leaking it. Rust does not offer especially strong controls for these except for pinned objects, and even then it feels like the controls are mostly a side effect of pinning.
> For 3, some objects only need to be pinned under certain circumstances, e.g. futures only need to be pinned after they're polled for the first time, but not before.
Is this actually useful in practice? (This is a genuine question, not a rhetorical question. But maybe let’s pretend that Rust had the cool ability to farm out initialization if uninitialized objects described in the OP: allowing access before pinning sounds a bit like allowing references to uninitialized data before initializing it.)
For #4, I’m not sure I have a real use case. Maybe I’ll try contemplating a bit more. Most I think that shared ^ exclusive is a neat concept but that maybe there’s room to extend it a little bit, and there isn’t any fundamental reason that a holder of an &mut reference needs to ensure that no one else can even identify the object while the &mut reference is live.
It's required to do any intialization, particularly for compound futures (e.g. a "join" or "select" type of combinator), since you need to be able to move the future from where it's created to where it's eventually used/polled. I assume some of those cases could be subsumed by &uninit if that existed yeah.
A type that might require stable pointers, like async{}, might want to be movable prior to use, so you don't want the type to require the value be pinned immediately. Or if you do, you need a construction like pinned-init that offers `&pin out T` - a pinned place that can be written to on initialisation of the type.