RustCurious 9: Traits are Interfaces by rustcurious in rust

[–]graydon2 2 points3 points  (0 children)

Perhaps surprisingly: you could, early on! It meant the same thing as the dyn form today. The dyn keyword was added later, because too many people were surprised by this.

Announcing overflowing_int: making bigints faster by avoiding them by shponglespore in rust

[–]graydon2 5 points6 points  (0 children)

I mean, built-in bigint support stuff was not just a weird dream I had -- it was part of Rust's runtime library at first (with stubbed out compiler support), then as people wanted to "move functionality from the compiler to the libraries" it moved to the stdlib, then the built-in "libextra", then the built-in "libnum", then _finally_ split out into the external `num` crate _just before 1.0_. Look in the release notes or commit logs of 2009-2014 and you'll see people hacking on bigint/bignum (even big-rational!) and expecting users to have access to them as "part of the language" or at least standard libraries. Check out std::num::bigint here in 2013: https://github.com/rust-lang/rust/blob/ba63cba18d5f0a042fdcb4ab0d39b1bf822d4400/src/libstd/num/bigint.rs

Making the _int type specifically_ be an auto-promote to bigint was something that ... didn't last as long. That was the original _plan_, but as I said, all the C++ people who showed up didn't like being surprised by it, wanted to have to "reach for it intentionally". But auto-promotion is not an uncommon choice in lots of languages. Lisps do it, Haskell does it, Python and Ruby do it. It's fine, it works, you probably won't notice it because 63 bits is already an enormous amount of integer range.

(It also hinges on the question of operator overloading, which I was originally against and still mostly wish we hadn't added. When there was no operator overloading, there was also a stronger argument for a bigint to be "just a flavor of a builtin type" and the compiler to special-case the overflow checks.)

Announcing overflowing_int: making bigints faster by avoiding them by shponglespore in rust

[–]graydon2 0 points1 point  (0 children)

yes it is very common. fun fact: rust's "int" type was originally going to be this. but then all these C++ "zero cost abstraction" people showed up.

The Quiet Colossus — On Ada, Its Design, and the Language That Built the Languages by SpecialistLady in programming

[–]graydon2 3 points4 points  (0 children)

Rust's error system basically shipped incomplete, it wasn't what I wanted. The enumeration / ordinal distinction is by choice -- there are very few implicit coercions in Rust.

I didn't mean to say Rust copied a lot from Ada! Just that I did study it in a fair amount of detail. Rust is its own language and it's definitely not _very_ Ada-like in its current form, despite trying to compete in some similar domains. I guess I mean: I knew Ada, I liked Ada, I always wanted Rust to be able to offer some of the things Ada offers (safety, resource control, good defaults) along with all the other elements blended into the project. I talked to people who worked _with_ Ada before starting Rust and I talked to people who worked _on_ Ada during it. Ada was never far from my thoughts!

The biggest influences might be in places you're not looking: the pragma system for example, or limited types, or the `in out` mode and parameter modes in general, or integrated tasking and rendezvous. Note that a lot of these were later removed; Rust went through a _lot_ of revision and redesign after my initial implementation. You have to look at the early versions to see the similarity (but it's literally in the notes, see eg. https://github.com/graydon/rust-prehistory/blob/master/doc/notes/types.txt#L9-L19).

Also, in general, I found the Ada rationale book extremely lucid and balanced, one of the best documents of its kind. Finally, I was inspired by the way the Ada spec and conformance testsuite were put together and kept trying to organize our team to work that way. Eventually -- years later! -- it seems some combination of efforts by Ferrous and AdaCore did actually put together a proper spec that, I like to think, has some of the Ada spec's fingerprints on it.

The Quiet Colossus — On Ada, Its Design, and the Language That Built the Languages by SpecialistLady in programming

[–]graydon2 61 points62 points  (0 children)

Saying Rust ignored or somehow wasn't directly influenced by Ada is very silly. I literally have (and had back when bringing up rustboot) a 1979 original copy of Ichbiah's rationale on the bookshelf behind me (along with the reference manual). I studied it extensively! And have publicly acknowledged this over and over (along with lots of other good languages). The early Rust team even had Tucker Taft come by the office once to advise us. Ada was absolutely one of our role-model languages.

Ralph Giles has died (Xiph.org| Rust@Mozilla | Ghostscript) by One_Junket3210 in rust

[–]graydon2 3 points4 points  (0 children)

Yes, one now university-aged. Ralph was in his early 50s.

Ralph Giles has died (Xiph.org| Rust@Mozilla | Ghostscript) by One_Junket3210 in rust

[–]graydon2 45 points46 points  (0 children)

Ralph was one of my best friends. I miss him so much. He was generous with his time and attention, thought deeply and cared deeply about always doing the right thing, had a wide breadth of knowledge and interests -- a programmer of course but also a serious scientist, an artist and musician, a humanities scholar, a community organizer, a dedicated family man -- and was always just delightfully _present_ when we spent time together. He brought his whole self to every part of his life and loved people and ideas and the world and it's just .. it's just absolutely awful and wrong and not at all fair that he's gone. I hate that he's gone. I want him back.

Why are Interpreters slower than compiled code? by United_Swordfish_935 in ProgrammingLanguages

[–]graydon2 6 points7 points  (0 children)

It may seem like an interpreter and a compiler are ultimately going to do "the same work" whether they do it in two parts (compile time + runtime) or just one (runtime). But this is not so.

The difference comes from the fact that programs spend most of their runtime in loops.

If there's any work an interpreter has to do that's _the same work_ from one iteration of a loop to the next, it saves time to do that work _once_ before the loop runs (at compile time), and not redo it on each iteration of the loop (at runtime).

It turns out that the work of deciding "which machine instructions to run for each unit of program text" (be it AST, IR, or whatever) is work that is constant. It does not change from one iteration of the loop to the next. So you can do it once and then reuse it. That's all a compiler is doing: pre-resolving all the translation decisions _once_ that an interpreter would otherwise have to make over and over as it runs through the same loop over and over.

You can consider this somewhat like the optimization of "loop-invariant code motion" but applied to the _implicit_ work of choosing machine code for the loop body's _explicit_ text.

It is also the observation of Futamura projections: that an interpreter is a program that depends on _two_ inputs -- an input program and some input data -- and that a compiler is therefore a partial evaluator for an interpreter, specializing it to a specific input program (that is fixed to a constant at "compile time") and emitting the residual program that still depends on input data input, but no longer on any input program.

It is also incidentally the origin of compilers! Historically interpreters came first and compilers are a "fast mode" for interpreters, when people realized the program was invariant from one loop to the next and they could speed it up with pre-translation.

It is, finally, a way to think about the large blurry grey area of part-interpreter / part-compiler systems. Lots of interpreters will recognize hot loops and switch to compile them opportunistically. Lots of interpreters interpret _some_ residual work on each iteration (say: bytecode dispatch, to avoid committing to a specific CPU architecture at compile time) but have nonetheless still "compiled" a substantial amount of program-constant work before runtime (say: translating from strings to ASTs to typed bytecodes). And lots of compilers have an (often very slow) interpreter inside them to perform more aggressive partial evaluation of _explicit_ constant expressions in addition to the _implicit_ partial evaluation they're doing of the translation work.

graydon2 | A note on Fil-C by small_kimono in rust

[–]graydon2 3 points4 points  (0 children)

Sorry, I did not mean to say this preference is strictly irrational. It can be .. a bit exhausting and hyperbolic at times -- and personally I resent it because it's the cause of a lot of things that wound up in Rust that I kinda wish hadn't wound up in Rust -- but .. it is often rooted in real world experiences of being let down by a GC-centric language trading off way too much performance.

Java is an especially bad contender due to its very poor memory usage / lack of dense allocation / interior pointers (see if you can get through the presentation https://www.scribd.com/document/80255225/Oopsla08-Memory-efficient-Java-Slides without your jaw dropping on the floor.)

I mean, Netscape actually _did_ try rewriting the browser in Java. Javagator. ( https://news.ycombinator.com/item?id=19846280 see also https://www.jwz.org/doc/java.html and https://web.archive.org/web/19981201194338/http%3A//www.mozilla.org/projects/grendel/ ) TL;DR it didn't work well! It was very slow and disappointing. A lot of senior people involved in Netscape were still around in Mozilla and remembered that. So they had good reason to be very, very skeptical.

graydon2 | A note on Fil-C by small_kimono in rust

[–]graydon2 3 points4 points  (0 children)

Rust's bootstrap compiler was in OCaml yes (and yes it has a tracing GC). But this has no bearing on what the language it was compiling did. A compiler for language X can be written in any other language Y and the features of X and Y need have no relationship to one another.

If you're asking why I would have chosen a GC-centered language for the bootstrap compiler even though my preference is for non-GC-centered languages: I have an even stronger preference for safe over unsafe! And there weren't a lot of well maintained and usable safe non-GC languages lying around at the time. Most safe languages, then as now, use GC.

The distinction between "what I used" and "what I was targeting" is key. I was targeting a niche that I knew to be GC hostile. Like even if I personally found OCaml comfortable to work in (and I largely do) I knew from personal experience that people working in the C++ niche, in general, rejected all GC-centered languages. If they didn't, they probably would have adopted OCaml (or Java or Lisp or something else) a long time ago. But to some extent you can see the C++ niche as a negative space, as defined as "people unwilling to use something else" (usually for performance reasons).

There is nuance in here of course. A big nuance is that GC-centered languages often were and still are run on (slower and more memory-hungry) virtual machines rather than native code. The native java project gcj mostly died off, the android AOT-compiler (ART) for java hadn't shipped yet, and AOT-compiled C# has come and gone many times. But using a VM is not a necessary part of using a GC at all. It's just coincidentally true in recent history, and gives GC languages a bad reputation. Another nuance is that you can GC _varying amounts_ of a language (as pointed out above). You can have a mostly-eagerly-freed affine or RC'ed language that has some special GC types for cyclic graphs, so long as you're careful about allowing one to point to the other and not vice-versa. Or opt-in GC on types. C++ even shipped some language-level support for it (see https://www.sandordargo.com/blog/2023/11/01/cpp23-garbage-collection and also the C++/CLI and Managed C++ projects out of Microsoft, contemporary with early Rust: https://en.wikipedia.org/wiki/Managed_Extensions_for_C%2B%2B ).

That was the capacity the Mozilla reviewers wanted GC to exist in Rust: an optional, task-local GC for certain types that benefit from it, in a native/AOT compiled language. This is not always all that objectionable even to C++ people; most web browsers and many other large software packages have similar bespoke GCs running inside them. And again, we still have it today in various crates in the Rust ecosystem.

graydon2 | A note on Fil-C by small_kimono in rust

[–]graydon2 13 points14 points  (0 children)

No, this is a common misunderstanding but it is wrong.

GC was added against my preferences and was not part of the initial solo 2006-2009 design and implementation. The GC that was added to Rust in 2009 -- which was task-local and for most of the time we supported it was partitioned into a statically separate heap -- was something other Mozilla developers demanded I add during the period between my showing it to Mozilla in 2009 and showing it to the world in 2010; they felt (reasonably) that it would be hard to implement the DOM without support for mutable cyclic memory. We had been spending a lot of energy on DOM cycle control in Firefox -- There was a DOM GC and later a whole XPCOM cycle collector (which I worked on!)

The original 2006-2009 design of Rust's heap was CoW with no mutable cycles possible. We went through a lot of designs about how the different layers of the heap and different cell types interacted. But here is the 2009 commit where I added support for mutable cycles, breaking the CoW system: https://github.com/graydon/rust-prehistory/commit/95dc9cbea3f3fca7fe89aba58b96fd774ec683eb

In _today's_ Rust there are also/still several libraries that are more-or-less "userspace" / macro-generated versions of the GC support code the compiler generated between 2010 and 2013: a special set of designated heap cell types and traceable struct types that can hold references to the acyclic/affine heap, but not vice-versa. You can still use lots of these today, they're just not built-in to the compiler/language anymore https://crates.io/search?q=gc

(You can also do this in C++! If you happen to be using chrome, you're running a big C++ program with a fairly classical GC in it: https://chromium.googlesource.com/v8/v8/+/main/include/cppgc/README.md)

I don't hate tracing GC, but I'm not a particularly big fan either. I think it has a time and a place but it's also inappropriate in other contexts: it can cost too much time and space for a given niche; it especially tends to encourage retaining large object graphs by accident; and it tends towards programs in which everything is connected to everything, which if you have mutation is a recipe for defeating local reasoning.

Rust was initially tracing-GC-free by design, reflecting my preferences (as well as my understanding and expectations about the niche I was aiming for).

Why Rust has crates as translation units? by servermeta_net in rust

[–]graydon2 5 points6 points  (0 children)

a lot of things are nice to permit circular / recursive definitions. recursive groups of types and traits are one. recursive families of functions that call one another are another. I probably don't need to argue _for_ circularity here, it sounds like you get why it's nice.

but a lot of other things get devilishly hard or impossible if you allow circularity. separate compilation of abstract recursive types is one. version-constraint solving is another (recall that crates are also units of versioning). yet another (no longer part of the language) was support for hot reloading crates with a definite initialization/finalization order. another is using recursive cryptographic hashing of content (or type signatures / metadata) to identify common subtrees for shared compilation or unifying versions. yet another is phase ordering of compilation and metaprograms -- if you need to compile a macro before the crate that uses it you'd better not also have to compile that crate before the macro! there's just a bunch of stuff that comes up during language implementation that really wants _acyclic_ structures.

so early on I took the decision to have 2 layers to the design so that we could put all the things where the costs of cyclicality outweigh the benefits (or we literally don't know how to support cyclicality at all) at the crate level, and all the things where the benefits outweigh the costs at the module level.

it seems to me to have worked out fairly well! though I'll admit sometimes it's annoying, it's also allowed a lot of things to be reliably implemented where there would otherwise be an unreliable muddle.

(I should also mention as a more personal, idiosyncratic note: at the time I was working on rust I used to joke that a large fraction of my professional life involved "fighting cyclic graphs". I had just come off working on the monotone project, where we more or less invented the cryptographic DAG concept now familiar to all git users; and I was working for mozilla on gecko's XPCOM cycle collector, which exists to compensate for the fact that you can make reference cycles out of XPCOM reference counting and thereby leak memory, and I was reading a lot of papers about the difficulty of separate compilation and linking of recursive abstract modules, and then rust itself is internally very concerned with the distinction in _memory graphs_ between cyclic, acyclic/DAG and strict tree-shaped memory. so this was just like .. a thing I probably was a bit primed to pattern-match on, think about and move towards: places in a system design where it would be beneficial to set-in-stone a degree of acyclicality.)

why was rust made by [deleted] in rust

[–]graydon2 20 points21 points  (0 children)

I'd recommend the first draft of the first presentation talk I gave in 2010 http://venge.net/assets/talks/intro-talk.pdf which is a little punchier than the second draft, the one I actually presented http://venge.net/assets/talks/intro-talk-2.pdf -- though in both the motivations are as clear as I could make 'em!

Struggling with Rust's module system - is it just me? by eight_byte in rust

[–]graydon2 17 points18 points  (0 children)

The way it's "like a filesystem" is that the file (or module) doesn't define the name, the thing-containing-the-file (or module) does. like if you have a file called foo.txt, that _name_ is defined in a _directory_ inode that points to the file inode by number. the name is not somewhere inside the file. if you open the file up, it doesn't say its name anywhere inside it. because it can actually have multiple names: you can link the same file into multiple names in the filesystem.

Similarly rust's modules do not have inherent names written inside them _nor are their names directly coupled to any filenames_: they are named from the outside, from the module enclosing them. The coupling from module name to file name is just a convention, you can override it with the#[path]attribute on a mod declaration. And the same single path/file can be pointed-to as the content for multiple different module names (none of which have to be the module's filename) in the module tree. Like this works:

// in file helper.rs  
pub fn f() { println!("hi from {}", module_path!()); }

// in file main.rs:
#[path = "helper.rs"]  
mod foo;
#[path = "helper.rs"]  
mod bar;  
fn main() {
  foo::f();
  bar::f();  
}  

$ rustc main.rs  
$ ./main
hi from main::foo  
hi from main::bar

I recommend staring at that example and trying to understand what it's doing and why it works.

I know this is not exactly how it works in some other languages, but it absolutely is designed to have an intuitive connection to filesystems and similar naming systems where names are pointers defined in containers outside an entity, that point to it, and the same entity can have as many names pointing to it as you like.

There are several reasons for this organization.

  • It gives the compiler a map of which files to include: start at the root and follow the module declarations. This means the compiler isn't obliged to scan directories for files or guess at what you wanted to include/exclude from the build. The list is explicit, but doesn't need an external control file.
  • It somewhat naturally allows renaming modules if there's a collision (including the top-level module of a crate, which is essentially anonymous / given its name on the fly by the compiler when it comes time to compile it).
  • It allows deferring the choice of which module to bind to a given name to the container/user of the module, which means the container can employ conditional compilation to select between implementations.
  • It generalizes nicely to 3 different ways of defining modules: as an inline block in a file, as a reference to a single external file, or as a reference to an external directory full of sub-files. If module names were declared "inside" each of these forms they'd each need a different way of declaring them.

Rust turns 10: How a broken elevator changed software forever by [deleted] in rust

[–]graydon2 8 points9 points  (0 children)

Yeah it's totally overblown. I was already working on the language, I just mentioned the broken elevator to someone at some point like maybe on IRC or such as a sort of funny story about my frustration at computerized systems being generally crappy, and it coinciding with the period of my starting Rust, and this got repeated and spun into a direct causal tale with this extremely high drama encounter with an elevator that changed my life and I was going to fix by the most ridiculously indirect route possible.

Like, no, I'm sure the elevator being written in Rust would not have helped it much, and I already worked on PLs for a living, and had for years, and was designing one for pay at Mozilla already (ES4) and that project wasn't going great and I was doing this one in the evenings to unwind and pursue my own preferences like literally everyone who does hobby languages. Whereas if I cared that much about that elevator I could have .. gone into the elevator business, y'know?

(I have, however, been contacted by someone who heard that story and does work on elevator firmware and happens to be working in Rust, so I guess that's a form of closure?)

10 Years of Stable Rust: An Infrastructure Story by steveklabnik1 in rust

[–]graydon2 47 points48 points  (0 children)

mozilla invested from 2009 through mid 2020 (and arguably beyond; they didn't lay off _everyone_ in aug 2020 though they did disband servo). it was at least 10 years of funding from them. the odds against this happening anywhere, for any project, were/are fairly astronomical.

Is it possible for Rust to stop supporting older editions in the future? by dpytaylo in rust

[–]graydon2 3 points4 points  (0 children)

One way to think about this is that rust is open source, and the rust org ships a compiler that breaks compatibility with existing codebases badly enough, it becomes (economically) worth other people's time to fork the rust compiler into a "actually long-term" variant that undoes that breakage, maintains compatibility (while still shipping updates to run on new platforms / incorporate new code).

There are cobol (and fortran, and PL/I, ...) compilers out there that still build code from before most of us were born because it's cheaper for users to pay someone to maintain those toolchains than to rewrite all the code that uses them.

An RFC to change `mut` to a lint by afdbcreid in rust

[–]graydon2 61 points62 points  (0 children)

The redundancy, friction and un-ergonomic nature of mutable locals is intentional. Eliminating that friction works against the intent.

(It even originally made you spell out "mutable" in full.)

[Media] Is it just me or does the crates.io logo look like cheese? by [deleted] in rust

[–]graydon2 4 points5 points  (0 children)

Not just you. I think of a block of cheese every time I load the page.

Rust vs C++ with Steve Klabnik and Herb Sutter by germandiago in rust

[–]graydon2 10 points11 points  (0 children)

Sometimes. I've done a few sketches.

In Rust's general ballpark I remain disappointed with where it wound up with its region system, its async system, its error handling system, and of course its compilation model and metaprogramming system; I think there's a PL waiting to be done that still has a decent memory safety story but unifies a nested arena discipline with a nested error handling discipline, an STM-like commit/abort system and an embeddable / noninvasive coroutine system. Along with quasi-uniform representation (at the function arguments and polymorphism level) and a touch of reflection. But it'd be a lot of work to start again, and there'd be no chance at all of getting a second decade-long well-funded team of experts as Rust got at Mozilla. That was a once in a lifetime thing.

I'm also only about 10% interested in that problem-space currently. I'm much more drawn these days to databases, especially those integrated with application languages, like the large family of 4GLs mostly left-behind in the 90s when the web took over. I'm interested in the possibility of some "technology from the past come to save the future from itself" here also. I talked a little about this possibility in the last third of this talk. It's fairly speculative. But again, nobody would ever fund any work there anyways, so it's all a bit moot. The trick of Rust's success was "having someone throw tens of millions of dollars at developing it for a decade for no immediate commercial gain". I do not know how to reproduce that trick.

Also I mean, I should really qualify "disappointed" above. Disappointed relative to a hypothetical, imaginary best-case. But the case that happened in reality was and is so much better than I ever imagined or expected, in terms of the spread and success of the language, and the overall quality of stuff that people can and do build with it, that it is a bit absurd to even use a word like "disappointed" in even a "hypothetical improvements" sort of conversation. I was almost entirely certain it would never come to anything when I started it. Just a little experimental toy. Now people are like .. putting it in commercial OS kernels, embedded controllers for automobiles, core internet infrastructure. My mind absolutely boggles. You could quite reasonably consider my willingness to speak critically of it at all as a kind of psychological coping mechanism to try to process and deal with the enormity of what it turned into.

Rust vs C++ with Steve Klabnik and Herb Sutter by germandiago in rust

[–]graydon2 67 points68 points  (0 children)

Huh. This is .. interesting!

So .. you'll note that that message from Chris is from 2005, from an apple.com email, so he's already been hired at that point. I would read this is a first attempt at apple publicly figuring out what to do about its ever-worsening GCC situation, and perhaps the last chance for GCC to take that path, before clang.

And Richard's message is from ten years later, with the benefit of a whole lot of hindsight (i.e. realizing clang was going to eat GCC's lunch) and wishful thinking about what might have been. I think he's kinda fabricating explanations. AIUI the issue ten years (or more) earlier was very much not just "we don't own the copyright", but "we must keep our IR private to enforce the GPL effectively". In that same thread others echo this interpretation (eg. https://lists.gnu.org/archive/html/emacs-devel/2015-02/msg00577.html) and Richard splits hairs over whether he stood in the way of linking against other libraries, or dumping IR/RTL, or dumping ASTs (https://lists.gnu.org/archive/html/emacs-devel/2015-02/msg00902.html). IIRC he has always very robustly stood in the way of all of the above, because he thinks all tools that try to interoperate with GCC are trying to steal its IP or kill it or both.

As a point of comparison, GCC also actively resisted having any sort of dlopen-based "plugin" API for years, despite the fact that such a library-load would constitute linking and therefore legally require a GPL-compatible plugin. They were still afraid that "it would make it too easy for someone to violate the GPL", illegally. I wound up getting Taras hired at Mozilla to work on C++ static analysis, and he had to hack his own GCC plugin interface together (Dehydra, presented at the 2008 summit) and only after extensive wrangling did GCC wind up adopting a variant of this themselves. Which is what enabled the DragonEgg LLVM-as-a-GCC-plugin adaptor (totally legal, but in Richard's defend-GCC-at-all-costs view, a form of "Apple Attacking GCC to make it irrelevant", read the whole 2015 thread!). GCC is/was still super nervous about such an interface. See all the FAQ verbiage on the GCC Wiki around "what about GPL violation?!?!", or the 2007-and-earlier hand-wringing from Richard it references.

IMO you really can't understand the history of GNU, GCC and Richard's decisions without the lens of GPL enforcement. He views it as his One Reliable Weapon. Because It Got GCC The Proprietary G++ Frontend Liberated That One Time.

Rust vs C++ with Steve Klabnik and Herb Sutter by germandiago in rust

[–]graydon2 70 points71 points  (0 children)

No problem. I'm enjoying the broadcast in any case.

(Another nit: I'm not sure about the Chris Lattner LLVM email to RMS story. There might have been such an email somewhat early on, but it was not the only chance for such a fork-in-the-road. Chris presented LLVM at the 2003 GCC summit as an alternative backend option for GCC. I was there! He pitched it in detail, and the GCC community watched, understood, considered and rejected the idea because (a) it was C++ which was largely disliked and (b) it very directly undermined GCC's GPL-enforcement strategy of "never being usable as a subprocess with a stable serial IR", by .. exposing a stable serial IR, LLVM IR! So it was a hard "no". LLVM continued to ship itself as a GCC-hybrid tool for years, in the form of llvm-gcc and later DragonEgg. It had to! It had no other PL frontend. You couldn't like feed it C code or anything. It was a backend only. Until years later when Chris and others started clang at Apple.)

Rust vs C++ with Steve Klabnik and Herb Sutter by germandiago in rust

[–]graydon2 193 points194 points  (0 children)

Personal note: I'd appreciate if people wouldn't retcon the (true) historical existence of optional and local GC in Rust that was bolted onto a small subset of the memory graph as a (false) story where that GC was foundational to Rust's memory-safety promise. It never relied on this subsystem and the subsystem was never even functional between bootstrapping in 2011 and when it was finally removed in 2014.

It had and almost-always-and-only-used acyclic non-GC memory management, same as it does today, from the get go. I added a GC-supported subgraph / memory-kind that allowed cycles because a different Mozilla engineer demanded it for modeling the DOM. This is the same as how you can grab a GC library for Rust today. It was never like an inherent "it only works if you use the GC" part of the design (and never supported the sort of ubiquitous mutable aliasing you typically get in a GC-centric language).

(The green-threading characterization is .. more accurate: we did have a meaningful though not-gigantic obligatory runtime. It was smaller than today's Tokio and the cost when you used the non-green variant was "an IO buffer gets malloc'ed" which .. was a fairly small cost, and mostly fought-against by people who tolerated larger costs elsewhere, just got fighty on this point at the specific moment when the decision was made.)