Skip to content(if available)orjump to list(if available)

The borrowchecker is what I like the least about Rust

eigenspace

I don't use Rust much, but I agree with the thrust of the article. However, I do think that the borrowchecker is the only reason Rust actually caught on. In my opinion, it's really hard for a new language to succeed unless you can point to something and say "You literally can't do this in your language"

Without something like that, I think it just would have been impossible for Rust to gain enough momentum, and also attract the sort of people that made its culture what it is.

Otherwise, IMO Rust would have ended up just like D, a language that few people have ever used, but most people who have heard of it will say "apparently it's a better safer C++, but I'm not going to switch because I can technically do all that stuff in C++"

gdbsjjdn

Agreed. As a comparison Golang was sold as "CSP like Erlang without the weird syntax" but people realized channels kind of suck and goroutines are not really a lot better than threads in other languages. The actual core of OTP was the supervisor tree but that's too complicated so Golang is basically just more concise Java.

I don't think this is a bad thing but it's a funny consequence that to become mainstream you have to (1) announce a cool new feature that isn't in other languages (2) eventually accept the feature is actually pretty niche and your average developer won't get it (3) sand off the weird features to make another "C but slightly better/different"

mr_00ff00

This is also somewhat backed up by the fact that OCaml (to my understanding) is basically GC Rust without a borrow checker, and yet it’s basically a hobby language.

nine_k

Rather, an academia language.

Also, OCaml had trouble with multithreading for quite some time, which was a limiting factor for many applications.

Facebook made a large effort to thrust OCaml into the limelight, and even wrote a nice alternative frontend (Reason). Sadly, it did not stick.

creata

I had the impression that SML was more popular in academia, and OCaml in industry.

Old but funny comparison: http://adam.chlipala.net/mlcomp/

coldtea

I wouldn't read too much into its lacking the borrow checker.

It's not about not having a C-like syntax (huge mainstream points lost), good momentum, and not having the early marketing clout that came from Rust being Mozilla's "hot new language".

amelius

Idiomatic programming in a functional language requires garbage collection. There is a reason languages like OCaml and Haskell have a garbage collector. Without it, programming in these languages would be completely different.

If you look at it from that perspective, then Rust is the hobby language.

mavelikara

The first version of Rust compiler, I think, was written in OCaml.

creata

I think there are two other big differences that also helped Rust become popular:

* Rust has a C++-flavored syntax, but OCaml has a relatively alien ML-flavored syntax.

* Rust has the backing of Mozilla, but I don't think OCaml had comparable industry backing. (Jane Street, maybe?)

dismalaf

Hobby language? Plenty of commercial and important software has been written in OCaml.

Hell, the early versions of the Rust compiler were written in OCaml...

vlovich123

How may be the wrong word, but it’s definitely a niche language and significantly less software is written in it

mr_00ff00

Maybe I’m wrong, but I only really know of Jane Street for OCaml, meanwhile FAANG all has at least some rust code.

Also I would argue the rust compiler started as a hobby project

littlestymaar

Realistically unless you want to work at Jane Street or Inria (the French computer science lab where Ocaml was made), if you want to use Ocaml, it's going to be as a hobby.

pyrale

> and yet it’s basically a hobby language.

The difference between academia languages such as ocaml or haskell and industry languages such as Java or C# is hundreds of millions of dollar in advertising. It's not limited to the academy: plenty of languages from other horizons failed, that weren't backed by companies with a vested interest in you using their language.

You should probably not infer too much from a language's success or failure.

coldtea

C, C++, Python, Perl, Ruby, didn't have "millions of dollars in advertising", and yet.

Java and C# are the only one's that fit this. Go and Rust had some publicity from being associated with Google and Mozilla, but they both caught on without "millions of dollars in advertising" too. Endorsement by big companies like MS came much later for Rust, and Google only started devoting some PR to Go after several years of it already catching momentum.

estebank

You're making it sound like the success of a language is determined purely by its advertising budget by pointing at languages that had financial backing, which disregards that financial backing allows for more resources to solve technical problems. Java and C# have excellent developer tools which wouldn't have existed in their current state without lots of money being thrown around, and the languages' adoption trajectory wouldn't have looked the way they did if their tooling hasn't been as good as it was. A new language with 3 people behind it can come up with great ideas and excellent execution, but if you can't get enough of the scaffolding built in order to gain development momentum and adoption, then it is very hard to become mainstream, and money can help with that.

roland35

I'm not sure.. without the borrow checker you could have a pretty nice language that is like a "pro" version of golang, with better typing, concise error handling syntax, and sum types. If you only use things like String and Arc objects, you basically can do this, but it'd be nice to make that not required!

eigenspace

That's my whole point. Without the borrow checker it would have been a nice language, but I believe it would not have gotten popular, because being nice isnt enough to be popular in the current programming language landscape.

MaulingMonkey

As a Rust fan, I 100% agree. I already know plenty of nice, "safe", "efficient" languages. I know only one language with a borrow checker, and that feature has honestly driven me to use it in excess.

Most of my smaller projects don't benefit so much from the statically proven compile time guarantees that e.g. Rust with it's borrow checker provide. They're simple enough to more-or-less exhaustively test. They also tend to have simple enough data models and/or lax enough latency requirements that garbage collectors aren't a drawback. C#? Kotlin? Java? Javascript? ??? Doesn't matter. I'm writing them in Rust now, and I'm comfortable enough with the borrow checker that I don't feel it slows me down, but I wouldn't have learned Rust in the first place without a borrow checker to draw me in, and I respect when people choose to pass on the whole circus for similar projects.

The larger projects... for me they tend to be C++, and haven't been rewritten in Rust, so I'm tormented with a stream of bugs, a large portion of which would've been prevented - or at least made shallow - by Rust's borrow checker. Every single one of them taunts me with how theoretically preventable they are.

zer00eyz

> without the borrow checker ... golang... concise error handling syntax

Except both of these things are that way for a reason.

The author talks about the pain of having other refactor because of the borrow checker. Every one laments having to deal with errors in go. These are features, not bugs. They are forcing functions to get you to behave like an adult when you write code.

Dealing with error conditions at "google scale" means you need every one to be a good citizen to keep signal to noise down. GO solves a very google problem: don't let JR dev's leave trash on at the campsite, force them to be good boy scouts. It is Conways law in action (and it is a good thing).

Rust's forced refactors make it hard to leave things dangling. It makes it hard to have weak design. If you have something "stable", from a product, design and functionality standpoint then Rust is amazing. This is sort of antithetical to "go fast and break things" (use typescript, or python if you need this). It's antithetical to written in the stand up requirements, that change week to week where your artifacts are pantomime and post it notes.

Could the borrow checker be better, sure, and so could errors in go. But most people would still find them a reason to complain even after their improvement. The features are a product of design goals.

ChadNauseam

> [The pain of the borrow checker is felt] when your existing project requires a small modification to ownership structure, and the borrowchecker then refuses to compile your code. Then, once you pull at the tiny loose fiber in your code's fabric, you find you have to unspool half your code before the borrowchecker is satisfied.

Probably I just haven't been writing very "advanced" rust programs in the sense of doing complicated things that require advanced usages of lifetimes and references. But having written rust professionally for 3 years now, I haven't encountered this once. Just putting this out there as another data point.

Of course, partial borrows would make things nicer. So would polonius (which I believe is supposed to resolve the "famous" issue the post mentions, and maybe allow self-referential structs a long way down the road). But it's very rare that I encounter a situation where I actually need these. (example: a much more common need for me is more powerful consteval.)

Before writing Rust professionally, I wrote OCaml professionally. To people who wish for "rust, but with a garbage collector", I suggest you use OCaml! The languages are extremely similar.

luckystarr

I believe it. I experienced this once, as I tried to have everything owned. Now I just clone around as if there's no tomorrow and tell myself I'll optimize later.

don-bright

Regarding Indexes: "When the same borrowchecker makes references unworkable, their solution is to... recommend that I manually manage them, with zero safety and zero language support?!?"

Language support: You can implement extension traits on an integer so you can do things like current_node.next(v) (like if you have an integer named 'current_node' which is an index into a vector v of nodes) and customize how your next() works.

Also, I disagree there is 'zero safety', since the indexes are into a Rust vector, they are bounds checked by default when "dereferencing" the index into the vector (v[i]), and the checking is not that slow for vast majority of use cases. If you go out of bounds, Rust will panic and tell you exactly where it panicked. If panicking is a problem you could theoretically have custom deference code that does something more graceful than panic.

But with using indexes there is no corruption of memory outside of the vector where you are keeping your data, in other words there isn't a buffer overflow attack that allows for machine instructions to be overwritten with data, which is where a huge amount of vulnerabilities and hacks have come from over the past few decades. That's what is meant by 'safety' in general.

I know people stick in 'unsafe' to gain a few percent speed sometimes, but then it's unsafe rust by definition. I agree that unsafe rust is unsafe.

Also you can do silly optimization tricks like if you need to perform a single operation on the entire collection of nodes, you can parallelize it easily by iterating thru the vector without having to iterate through the data structure using next/prev leaf/branch whatever.

noodletheworld

> If you go out of bounds, Rust will panic and tell you exactly where it panicked

This argue has a long history.

It is a widely used pattern in rust.

It is true that panics are memory safe, and there is nothing unsafe about having your own ref ids.

However, I believe thats its both fair and widely acknowledged that in general this approach is prone to bugs that cause panics for exactly this reason, and thats bad.

Just use Arc or Rc.

Or, an existing crate that implements a wrapper around it.

Its enormously unlikely that most applications need the performance of avoiding them, and very likely that if you are rolling your own, youll get caught up by edge cases.

This is a prime example of a rust antipattern.

You shouldnt be implementing it in your application code.

IncRnd

The point is to prove to the compiler that the two mutable references are disjoint. That's what the error means.

Rust could be instructed as follows:

fn main() {

    let mut point = Point { x: 1.0, y: 2.0 };

    let Point { x: ref mut x_ref, y: ref mut y_ref } = point;
    *x_ref *= 2.0;
    *y_ref *= 2.0;
}

palata

I choose a language that is as ergonomic as possible, but as performant as necessary. If e.g. Kotlin is fine, there is no way I will choose Rust.

Many projects are written in Rust that would absolutely be fine in Go, Swift or a JVM language. And I don't understand: it is nicer to write in those other languages, why choose Rust?

On the other hand, Rust is a lot nicer than C/C++, so I see it as a valid alternative there: I'm a lot happier having to make the borrow-checker happy than tracking tricky memory errors in C.

Animats

One of his examples of a borrow checker excess:

    struct Id(u32);

    fn main() {
        let id = Id(5);
        let mut v = vec![id];
        println!("{}", id.0);
    }
isn't even legit in modern C++. That's just move semantics. When you move it, it's gone at the old name.

He does point out two significant problems in Rust. When you need to change a program, re-doing the ownership plumbing can be quite time-consuming. Losing a few days on that is a routine Rust experience. Rust forces you to pay for your technical debt up front in that area.

The other big problem is back references. Rust still lacks a good solution in that area. So often, you want A to own B, and B to be able to reference A. Rust will not allow that directly. There are three workarounds commonly used.

- Put all the items in an array and refer to them by index. Then write run-time code to manage all that. The Bevy game engine is an example of a large Rust system which does this. The trouble is that you've re-created dangling pointers, in the form of indices kept around after they are invalid. Now you have most of the problems of raw pointers. They will at least be an index to some structure of the right type, but that's all the guarantee you get. I've found bugs in that approach in Rust crates.

- Unsafe code with raw pointers. That seldom ends well. Crates which do that are almost the only time I've had to use a debugger on Rust code.

- Rc/RefCell/run-time ".borrow()". This moves all the checking to run time. It's safe, but you panic at run time if two things borrow the same item.

This is a fundamental problem in Rust. I've mentioned this before. What's needed to fix this is an analyzer that checks the scope of explicit .borrow() and .borrow_mut() calls, and determines that all scopes for the same object are disjoint. This is not too hard conceptually if all the .borrow() calls produce locally scoped results. It does mean a full call chain analysis. It's a lot like static detection of deadlock, which is a known area of research [1] but something not seen in production yet.

I've discussed this with some of the Rust developers. The problem is generics. When you call a generic, the calling code has no idea what code the generic is going to generate. You don't know what it's going to borrow. You'd have to do this static analysis after generic expansion. Rust avoids that; generics either compile for all cases, or not at all. Such restricted generic expansion avoids the huge compile error messages from hell associated with C++ template instantiation fails. Post template expansion static analysis is thus considered undesirable.

Fixing that could be done with annotation, along the lines of "this function might borrow 'foo'". That rapidly gets clunky. People hate doing transitive closure by hand. Remember Java checked exceptions.

This is a good PhD topic for somebody in programming language theory. It's a well-known hard problem for which a solution would be useful. There's no easy general fix.

[1] https://dl.acm.org/doi/10.1145/3540250.3549110

creata

> The trouble is that you've re-created dangling pointers

That's true, but as a runtime mitigation, adding a generational counter (maybe only in debug builds) to allocations can catch use-after-frees.

And at least it's less likely to be a security vulnerability, unless you put sensitive information inside one of these arrays.

cmrdporcupine

I always say for people coming from C++ ... just imagine a std::move is there around everything (well, except for things that are Copy) ... then it will all make sense.

The problem is this mental model is entirely foreign to people who have worked in literally every other language where pass by value (copy0 or pass by reference are the way things work, always.

jltsiren

This reminds me of something that was popular in some bioinformatics circles years ago. People claimed that Java was faster than C++. To "prove" that, they wrote reasonably efficient Java code for some task, and then rewrote it in C++. Using std::shared_ptr extensively to get something resembling garbage collection. No wonder the real Java code was faster than the Java code written in C++.

I've been writing C++ for almost 30 years, and a few years of Rust. I sometimes struggle with the Rust borrow checker, and it's almost always my fault. I keep trying to write C++ in Rust, because I'm thinking in C++ instead of Rust.

The lesson is always the same. If you want to use language X, you must learn to write X, instead of writing language Y in X.

Using indexes (or node ids or opaque handles) in graph/tree implementations is a good idea both in C++ and in Rust. It makes serialization easier and faster. It allows you to use data structures where you can't have a pointer to a node. And it can also save memory, as pointers and separate memory allocations take a lot of space when you have billions of them. Like when working with human genomes.

timmytokyo

If using indices is going to be your answer, then it seems to me you should at least contend with the OP's argument that this approach violates the very reason the borrowchecker was introduced in the first place.

From the post:

"The Rust community's whole thing is commitment to compiler-enforced correctness, and they built the borrowchecker on the premise that humans can't be trusted to handle references manually. When the same borrowchecker makes references unworkable, their solution is to... recommend that I manually manage them, with zero safety and zero language support?!? The irony is unreal."

mmoskal

I think this is like unsafe - most of your code won’t have it, so you get the benefits of borrow checker (memory safety and race freedom) elsewhere.

alilleybrinker

For the disjoint field issues raised, it’s not that the borrow checker can’t “reason across functions,” it’s that the field borrows are done through getter functions which themselves borrow the whole struct mutably. This could be avoided by making the fields public so they can be referenced directly, or if the fields needs to be passed to other functions, just pass the the field references rather than passing the whole struct.

There are open ideas for how to handle “view types” that express that you’re only borrowing specific fields of a struct, including Self, but they’re an ergonomic improvement, not a semantic power improvement.

mirashii

> For the disjoint field issues raised, it’s not that the borrow checker can’t “reason across functions,” it’s that the field borrows are done through getter functions which themselves borrow the whole struct mutably

Right, and even more to the point, there's another important property of Rust at play here: a function's signature should be the only thing necessary to typecheck the program; changes in the body of a function should not cause a caller to fail. This is why you can't infer types in function signatures and a variety of other restrictions.

JoshTriplett

Exactly. We've talked about fixing this, but doing so without breaking this encapsulation would require being able to declare something like (syntax is illustrative only) `&mut [set1] self` and `&mut [set2] self`, where `set1` and `set2` are defined as non-overlapping sets of fields in the definition of the type. (A type with private fields could declare semantic non-overlapping subsets without actually exposing which fields those subsets consist of.)

sowbug

majormajor

This seems to be a golden rule of many languages? `return 3` in a function with a signature that says it's going to return a string is going to fail in a lot of places, especially once you exclude bolted-on-after-the-fact type hinting like what Python has.

It's easier to "abuse" in some languages with casts, and of course borrow checking is not common, but it also seems like just "typed function signatures 101".

Are there common exceptions to this out there, where you can call something that says it takes or returns one type but get back or send something entirely different?

saghm

It's super easy to demonstrate your point with the first example the article gives as well; instead of separate methods, nothing prevents defining a method `fn x_y_mut(&mut self) -> (&mut f64, &mut 64)` to return both and use that in place of separate methods, and everything works! This obviously doesn't scale super well, but it's also not all that common to need to structure this way in the first place.

vineethy

The author's motivation for writing this is well-founded. However, the author doesn't take into account the full spirit of rust and the un-constructive conclusion doesn't really help anyone.

A huge part of the spirit of rust is fearless concurrency. The simple seeming false positive examples become non-trivial in concurrent code.

The author admits they don't write large concurrent - which clearly explains why they don't find much use in the borrow checker. So the problem isn't that the rust doesn't work for them - it's that a central language feature of rust hampers them instead of helping them.

The conclusion for this article should have been: if you're like me and don't write concurrent programs, enums and matches are great. The language would be work better for me if the arc/box syntax spam went away.

As a side note, if your code is a house of cards, it's probably because you prematurely optimized. A good way to get around this problem is to arc/box spam upfront with as little abstraction as possible, then profile, then optimize.

Fraterkes

I think there's a lot love for the borrowchecker because a lot of people in the Rust community are working on ecosystems (eg https://github.com/linebender) which means they are building up an api over many years. In that case having a very restrictive language is really great, because it kinda defines the shape the api can have at the language level, meaning that familiarity with Rust also means quick familiarity with your api. In that sense it doesn't matter if the restrictions are "arbitrary" or useful.

The other end of the spectrum is something like gamedev: you write code that pretty explicitly has an end-date, and the actual shape of the program can change drastically during development (because it's a creative thing) so you very much don't want to slowly build up rigidity over time.

IshKebab

This post pretty much completely ignores the advantages of the borrow checker. I'm not talking about memory safety, which is it's original purpose. I'm talking about the fact that code that follows Rust's tree-style ownership pattern and doesn't excessively circumvent the borrow checker is more likely to be correct.

I don't think that was ever the intent behind the borrow checker but it is definitely an outcome.

So yes, the borrow checker makes some code more awkward than it would be in GC languages, but the benefits are easily worth it and they stretch far beyond memory safety.

eigenspace

He's not ignoring them. The point of the article is that the author doesn't experience those things as concrete advantages for them. Like sure, there are advantages to those things, but the author says he doesn't feel it's worth the trouble in his experience for the sorts of code he's writing.

nine_k

One good RCE in production could alter this perception quite a bit. "The mosquito repellent is useless, I see too few mosquitos around me anyway."

eigenspace

The author is a bioinformatician writing scientific software, and often switching back and forth between Rust, Julia, and Python. His concerns and priorities are not the same as people doing systems-level programming.

ActorNightly

> is more likely to be correct.

This is a moot statement. Here is a thought experiment that demonstrates the pointlessness of languages like Rust in terms of correctness.

Lets say your goal is ultimate correctness - i.e for any possible input/inital state, the program produces a known and deterministic output.

You can chose 1 of 2 languages to write your program in:

First is standard C

Second is an absolutely strict programming language, that incorporates not only memory membership Rust style, but every single object must have a well defined type that determines not only the set of values that the object can have, but the operations on that object, which produce other well defined types. Basically, the idea is that if your program compiles, its by definition correct.

The issue is, the time it takes to develop the program to be absolutely correct is about the same. In the first case with C, you would write your program with carefully designed memory allocation (something like mempool that allocates at the start), you would design unit tests, you would run valgrind, and so on.

In the second case, you would spend a lot more time carefully designing types and operations, leading to a lot of churn of code-compile-fix error-repeat, taking you way longer to develop the program.

You could argue that the programmer is somewhat incompetent (for example, forgets to run valgrind), so the second language will have a higher change of being absolutely correct. However the argument still holds - in the second language, a slightly incompetent programmer can be lazy and define wide ranging types (similar to `any` in languages like typescript), leading to technical correctness, but logic bugs.

So in the end, it really doesn't matter which language you chose if you want ultimate correctness, because its all up to the programmer. However, if your goal is rapid prototyping, and you can guarantee that your input is constrained to a certain range, and even though out of range program will lead to a memory bug or failure of some sort, programming in something like C is going to be more efficient, whereas the second language will force you write a lot more code for basic things.

ashdksnndck

This claim makes some sense to me if your development life cycle is: write and compile once, never touch again.

Working at a company with lots of systems written by former employees running in production… the advantages of Rust become starkly obvious. If it’s C++, I walk on eggshells. I have to become a Jedi master of the codebase before I can make any meaningful change, lest I become responsible for some disaster. If it’s Rust, I can just do stuff and I’ve never broken anything. Unit tests of business logic are all the QA I need. Other than that, if it compiles it works.

mikepurvis

There's the practical end goal benefit of safer and more robust programs, but I think there's also the piece that pg talks about in Beating The Averages which is that learning how to cooperate with these conventions and think like there's the borrow checker there makes you a better programmer even when you return to languages that don't have it.

al_fanta

> makes you a better programmer

If a language is bad, but you must use it, then yes learn it. But, if the borrowchecker is a source of pain in Rust, why not andmit it needs work instead of saying that “it makes you better”?

I’m not going to start writing brainfuck because it makes me a better programmer.

lblume

I believe that codebases written in Rust, with borrow checking in mind, are often very readable and allow local reasoning better than most other languages. The potential hardness might not stem from "making people better programmers" but from "making programmers write better code, perhaps at the cost of some level of convenience".

IshKebab

We do admit it needs work. The issues the author highlights can be annoying, a smarter borrow checker could maybe solve them.

The point is the borrow checker has already gone beyond the point where the benefits outweigh those annoyances.

It's like... Static typing. Obviously there are cases where you're like "I know the types are correct! Get out of my way compiler!" but static types are still vastly superior because of all the benefits they convey in spite of those occasional times when they get in the way.

pie_flavor

If you notice a rule for the first time, restricting what you want to do and making you jump through a hoop, it can be hard to see what the rule is actually for. The thrust of the piece is 'there should not be so many rules, let me do whatever I want to do if the code would make sense to me'. This does not survive contact with (say) an Enterprise Java codebase filled with a billion cases of defensive strict immutability and defensive copies, because the language lacks Rust's rules and constructs about shared mutability and without them you have to use constant discipline to prevent bugs. `derive(Copy)` as One More Pointless Thing You Have To Do only makes sense if you haven't spent much time code-reviewing someone's C++.

If you try to write Java in Rust, you will fail. Rust is no different in this regard from Haskell, but method syntax feels so friendly that it doesn't register that this is a language you genuinely have to learn instead of picking up the basics in a couple hours and immediately start implementing `increment_counter`-style interfaces.

And this is an inexperienced take, no matter how eloquently it's written. You can see it immediately from the complaint about CS101 pointer-chasing graph structures, and apoplexy at the thought of index-based structures, when any serious graph should be written with an index-based adjacency list and writing your own nonintrusive collection types is pretty rare in normal code. Just Use Petgraph.

A beginner is told to 'Just' use borrow-splitting functions, and this feels like a hoop to jump through. This is because it's not the real answer. The real answer is that once you have properly learned Rust, once your reflexes are procedural instead of object-oriented, you stop running into the problem altogether; you automatically architect code so it doesn't come up (as often). The article mentions this point and says 'nuh uh', but everyone saying it is personally at this level; 'intermittent' Rust usage is not really a good Learning Environment.

aapoalas

To the author, I would be a borrow checker apologist or perhaps extremist. I will take that mantle gladly: I am very much of the opinion that a systems programming language without a borrow checker[^1] will not find itself holding C++-like hegemony anymore (once/if C++ releases the scepter, that is). I guess I would even be sad if C++ kept the scepter for the rest of my life, or was replaced by another language that didn't have something like a borrow checker.

It doesn't need to be Rust: Rust's borrow checker has (mostly reasonable) limitations that eg. make some interprocedural things impossible while being possible within a single function (eg. &mut Vec<u32> and &mut u32 derived from it, both being used at the same time as shared references, and then one or the other being used as exclusive later). Maybe some other language will come in with a more powerful and omniscient borrow checker[^1], and leave Rust in the dust. It definitely can happen, and if it does then I suppose we'll enjoy that language then.

But: it is my opinion that a borrow checker is an absolutely massive thing in a (non-GC) programming language, and one that cannot be ignored in the future. (Though, Zig is proving me wrong here and it's doing a lot of really cool things. What memory safety vulnerabilities in the Ziglang world end up looking like remains to be seen.) Memory is always owned by some_one_, its validity is always determined by some_one_, and having that validity enforced by the language is absolutely priceless.

Wanting GC for some things is of course totally valid; just reach for a GC library for those cases, or if you think it's the right tool for the job then use a GC language.

[^1]: Or something even better that can replace the borrow checker; maybe Graydon Hoare's original idea of path based aliasing analysis would've been that? Who knows.

creata

> just reach for a GC library for those cases

Imo a GC needs some cooperation from the language implementation, at least to find the rootset. Workarounds are either inefficient or unergonomic. I guess inefficient GC is fine in plenty of scenarios, though.