The whole time I was like "what in the absolute hell is a ML language".
Honestly kind of shocked to see this type of mentality...wow.
If you don’t know ML, knowing what it stands for won’t help you. It’s like JS, either you know what JS is or you don’t. It’s not about what it means, it’s about what it is.
> A large part of why many people love Rust is that it's the first time they've used an ML family language.
You wouldn’t search "ML" but "ML family language".
Or copy paste the whole thing and ask ChatGPT what ML means in this context.
ML = Machine Learning
ML = Machine Language
ML = Markup Language
ML = Meta Language
And I always see ML in the Meta Language sense to be the biggest culprit where writers assume the reader knows it, even though it's probably the least widely known.
Ugh, “Arc”, aka (automatic) reference counting, is an implementation of GC as well. It may have certain characteristics, like having worse throughput, corner cases, and better predictability/latency characteristics, but it's GC nonetheless. That's not manual memory management. Swift is not a non-GC language.
And that's the big issue that people are missing — Rust tackles some hard problems regarding memory management, and a friendlier alternative exists: garbage collection.
This paper makes a strong argument that “pure” tracing garbage collection and “rote” reference counting are effectively duals at the opposite ends of a spectrum of implementation choices.
For example, GC’s often optimize differently for young vs. old objects. How do they distinguish these? By “counting” (one bit) their references across one or more traces in a series of sweeps.
What you are equating with "garbage collection" is Mark-Sweep, which is an another way to implement GC, with a very different set of tradeoffs. (Broadly, better throughput at the expense of higher latency.)
You can call a pointer to a value a "one-element static array."
This feels like people arguing that atheism is a kind of religion. Borrow checking and a library of safe default primitives make it so you don't really have to think about memory management; except to the extent that you generally stick to those primitives, and that you have to install the borrow checker into your own head to write Rust comfortably (although the compiler will help.)
Rust doesn't have any garbage collection, but it can just assume how you would want to deal with memory by forcing you to be very specific about when values should be created or destroyed.
To nitpick, the first language to bring non-GC automatic memory management to the mainstream would be either Forth or C through stack memory.
For heaps, we have reference counting, often handled automatically through the stack (e.g., C++ smart pointers).
The thing Rust brought to mainstream is not automatic memory management - they're a dime a dozen with and without GC. It's an ownership model that is strict about whether you have read or write access within the type system. At the same time, this is also where most developer friction stems from, as it's quite unique, and you're often faced with a trade-off between explaining exactly what you want at compile-time at higher design cost, or bailing out more to runtime checks with e.g. RefCell while getting fewer guarantees about code correctness from the compiler.
All with stack allocation.
I can’t really comment on what counted as mainstream before C though. Not old enough for that.
Goggle, Bing, DuckDuckGo, ChatGPT,... will gladly provide a list.
To define what was mainstream we'd need to talk to people coding at the time and ask them about popularity, adoption, tooling availability, etc. and what languages people were likely to use rather than what was available.
And no, I have no need for researching that as I have no use of the information - the point is to say that automatic memory management in mainstream languages predate rust by decades and is present in most languages, not "this specific language should get credit".
Besides, any Assembly programmer is well aware of what stack memory is all about, bring us even further back than 1958.
The whole point was that these well-known mainstream languages provided the feature claimed to first have arrived in Rust, to make it clear that it was not just theoretical or some obscure language.
It's not my fault that you misunderstood the topic or the idea of what makes something mainstream, and instead read this as an elaborate history class stating the absolute first discovery of the idea.
> Besides, any Assembly programmer is well aware of what stack memory is all about, bring us even further back than 1958.
The instructions you refer to were designed to support stack-based languages, not the other way around.
Stack memory is decades older than C.
Emphasis mine.
It's not C. You do manual memory management there. You have to call malloc/free.
Didn't use Forth, so I can't say anything about it.
Stack based allocation are essentially registers, right?
That you also have access to manual memory on the heap doesn’t matter. You can also do manual memory management in Rust if you want, as one has to do at times.
It's not rocket science. If you are calling malloc/free to maintain your memory you're doing manual memory management.
Sure, and when I do the same in Rust I'm also doing manual memory management. So by your definition, both Rust and C are manual memory languages.
> You're confusing default memory management with what's possible.
Ah, so we care about the default, which I pressume is what the language semantics themselves provide, rather than focusing on what the standard libraries can provide you?
In that case C is an automatic memory language, because the language semantics only provide you stack memory. malloc/free are just random functions in the standard library after all, just like Rust's `Box::new` and `std::alloc`.
See, the point is that you're opting into manual memory management in C from an automatic model. We are so used to the stack that we forget that it's the OG fully automatic zero-cost memory management system, and in case of C++, can be used to implement fully automatic heap memory as well - in which case you never need to call malloc/free/new/delete.
(And no, it doesn't count that your smart pointer calls new/delete, because then you also need to count Box::new calling std::alloc)
> By your logic, Arena allocators can be used in many different languages, including GC ones like Java.
Uh, even in bog standard Java without any shenanigans you are using an "arena allocator". A GC doesn't change how allocators work, it just responsible for calling free.
(Caveat about moving vs. non-moving garbage collectors and ones that have multiple arenas, but that's not relevant here and an entire topic of its own.)
You're being very thick on purpose. In Rust you need to reach for foreign functions to implement malloc/free.
> So by your definition, both Rust and C are manual memory languages.
No. By my definition what is the default semantics determines if it's manual or automatic.
But if you want to play these semantics game, you just admitted C is GC language and thus unsuitable for kernel development.
> Uh, even in bog standard Java without any shenanigans you are using an "arena allocator".
No, no you aren't. At least not explicitly. I assume you mean GC, if it has or doesn't have arenas is implementation detail.
I think you're mistakenly thinking of calling out to the (rust-lang maintained) libc crate's malloc/free functions. That's not the case - the standard library provides `std::alloc`, which is the allocator also backing Box and Vec.
> No. By my definition what is the default semantics determines if it's manual or automatic. It's CS 101.
Your definition of default semantics - "in C it's not what the language does but what happens when you call random library functions, while in Rust it's the opposite" - makes no sense at all.
C isn't considered a manual language because of default semantics, but because people have chosen to mainly rely on such paradigm.
> No, no you aren't. At least not explicitly. I assume you mean GC, if it has or doesn't have arenas is implementation detail.
Yes, whether any allocator has an arena is an implementation detail.
Whether you call `malloc` or `new`, or you have Go or Java do a heap allocation for you (which, to nitpick, is not actually the job of the garbage collector), the use of an arena is an implementation detail. In case of GC, the availability of optimizations also depend heavily on whether it's a moving GC.
Again, emphasis on the word, default. How are you using Box and Vec. Are you by default encouraged to drop them manually via Allocator?
No, you aren't. You're heavily discouraged via usage of `unsafe`, you are discouraged because the compiler does automatic `alloc`/`drop`.
You have to go out of your way to do manual memory management.
> Your definition of default semantics
I showed you what happens when you apply the non-default semantics of a language to C. You end up with nonsensical statements like C is a GC language. Just because a style is possible in language X doesn't mean language X is of that style.
> C isn't considered a manual language because of default semantics, but because people have chosen to mainly rely on such paradigm.
And why is that? Because the default affordance of the language makes it so that way of usage is the most natural to most users. You could put everything in the stack, but that would be extremely torturous for most users, so they use malloc/free.
You give people a knife, of course they will grab it by the handle.
{
int8_t x = 1;
}
allocates a byte on the stack, binds it to a variable named x, sets it to 1, then deallocates it from the stack. There is no explicit allocation or deallocation.As an optimization it can be put into a register instead of the stack, again without explicit allocation and deallocation (this is done by the compiler playing musical chairs with registers).
I would not consider this manual. Manual would instead be something like in embedded programming
int8_t* y = (int8_t*)0xB4DC0FF3;
*y = 1;
because one needs to keep track of used memory locations (do allocation management)Rust's memory management is not automatic, you have to explicitly manage memory. It's just made extremely easy for you by the language thanks to stuff like RAII, and there is checks that prevent memory safety violations like double free. But those checks won't prevent leaks, although the many lints do make it harder to forget about a value.
Also, Swift's refcounting can be seen as a form of gc.
By this logic no language on earth has automatic memory management. I've spent time troubleshooting a memory leak in JavaScript in the past month, caused by someone keeping a pointer around longer than necessary.
Rust's memory management is automatic in that you can write entire Rust programs without once calling `free` manually. I'm not sure what definition of automatic you're working off of, but if it excludes JavaScript it doesn't seem especially useful.
Personally I'd put the bar around not requiring people to distinguish different pointer types (owned vs shared). Languages like Java, JS, Python, Swift, Go, etc all have this "everything is a smart pointer" paradigm, (or at least the strong and widely used default). I'd say Rust is manually managed because you need to think about which pointer type to use and using gc'd pointer types like Arc has a syntax overhead (like say when a pointer is cloned).
Rust makes the distinction between stack and heap references, like C++. Other, more high-level languages don't - there's only one kind of reference, you can't take a reference to a stack object. Maybe you implement that by making all objects heap allocating (Java) or you just say they have to be copied every time (C# struct). That's really where the difference is.
There's a lot of juicy, juicy performance there. The problem is taking references to stack variables is problematic. Tracking heap objects with a GC or a ref counter is really trivial in comparison IMO, at least when you try to combine the systems.
That's what I meant by "strong and widely used default". Defaults and syntactic sugar do matter and influence the type of code written.
Not only instance methods on C# structs are implicitly byref, you can easily pass structs by reference via ref, out and in keywords. On top of that, ref structs can hold `byref` pointers aka 'ref' keyword which can point to arbitrary memory, or have references to other structs/variables/anything.
There is also regular C syntax with &T and T* for unmanaged references/pointers. On top of that, .NET's compiler has gotten very good at struct optimizations and pretty much ensures they stay in registers all the time unless address-taken, including SIMD registers for Vector<T> and Vector128/256/512<T> even their "deconstructed" form when specified width is not supported so they get handled as e.g. 256x2. There was a big jump in codegen quality in .NET 8 which can now sometimes trade blows with GCC and Clang around struct optimizations.
All these features are first-class and are heavily used by all kinds of performance-sensitive code.
Also, structs can implement interfaces and can be generic arguments that satisfy interface constraints, which works exactly like generics with trait bounds in Rust - you get a generic instantiation aka monomorphized function body, making the abstraction zero-cost.
> you can easily pass structs by reference via ref, out and in keywords. On top of that, ref structs can hold `byref` pointers aka 'ref' keyword which can point to arbitrary memory
These are not features I've encountered. I wonder how you solve dangling references when those references could point to automatic stack variables.
The handling is transparent, in a way. As implemented by CoreCLR, class-type generic arguments have shared representation named __Canon. This means that, for example, a `Dictionary<int, string>` has a generic instantiation indicated as `Dictionary<int, __Canon>` where __Canon is an implicit generic type argument passed alongside relevant calls. Statics referencing that do get exact address, and there is quite a bit of complexity regarding runtime handling of this as far as virtual calls and other edge cases are involved, but as a programmer you are never exposed to that directly. It's an implementation detail, and even un-monomorphized cases work rather fast in most situations, like standard data containers.
> These are not features I've encountered. I wonder how you solve dangling references when those references could point to automatic stack variables.
Not sure what you mean by automatic stack variables, but the idea behind byref pointers\managed references\'ref's is that they are not allowed to be boxed or otherwise placed on the heap.
This lifetime restriction enables key scenarios:
- byrefs can point to object interiors without hindering GC throughput
- byrefs can point to stack memory, allowing `var span = (stackalloc byte[32]);` and more
- byrefs can point to any unmanaged memory without requiring excessive range checks by GC
This way you can use `ref T` to represent any memory location and `Span<T>` to represent any contiguous memory range up to 2B elements, without having to carry around bespoke overloads and types that disambiguate between containers and memory sources, much like you would usually see in most other GC-based languages.
There is also additional lifetime analysis in Roslyn to prevent you from returning 'scoped' 'ref's to an outer scope, like having a ref point to an integer in the current method body and returning it to the caller - this will not compile, unless you override it with unsafe [UnscopedRef] which you should never do unless you are absolutely certain (every time I used it and was absolutely certain, the compiler was right and I was not :D). This also works "through" ref structs and other tricky scenarios, which means you can return a ref that points to a middle of heap-allocated array - the scope of object exceeds current method, and byref can keep the array rooted even if no other reference to it exists. The main restriction is byrefs can mostly flow "downward" as not to escape the scope they originate from, but that's a given in most scenarios in Rust just as much.
There is a basic walkthrough about byrefs here: https://learn.microsoft.com/en-us/dotnet/csharp/language-ref...
C# is good at systems programming - it also has portable SIMD and intrinsics, static linking, native compilation and zero-cost FFI. Engineers getting surprised by this fact rather than upset is the happier but unfortunately less frequent outcome :)
By restricting them, to, essentially, disallow storing `ref`s on the heap.
This is contrast to something like C/Zig, where things are fully manual, or something like Python or Javascript where things are fully handled for you.
In JavaScript's case it's an implementation detail, but one that is extremely relevant when maintaining something in production.
[0] As a silly example: a JavaScript implementation could technically store object references as a URL of an API that allows interacting with the object, as long as this is transparent to the user and the program behaves the same as a pointer implementation (minus non-functional characteristics like performance).
You can use things like WeakMap or WeakRef as one solution, but there are usually better options
If you mean "drop," it's just syntactic sugar for calling a trait that manually deallocates the memory and that you're free to reimplement.
It feels like people are equating "manual memory management" with "onerous memory management." People are usually going to want to do the boring thing with memory, and everything in the standard library by default does the boring thing with memory. If you write boring structs and enums, you'll derive boring memory management. But it's not part of the language, it's part of the library.
I meant `free` because I'm contrasting with C.
> It feels like people are equating "manual memory management" with "onerous memory management."
Manual memory management is onerous, but I'm very specifically talking about manual. If I can write a whole program without thinking about memory, the language does not require manual memory management. It may support it, but it doesn't require it the way that C or Zig do. You do not "have to explicitly manage memory" as OP claimed.
It would be unacceptable to allow this because Drop::drop says it only takes a mutable reference &mut T (and indeed it does) but now the thing is destroyed, so, that wasn't just a mutable reference at all!
You can call core::mem::drop<T> but well, look at it, here's the code:
pub fn drop<T>(_x: T) {}
Like, duh, we give it a T and then it doesn't give anything, the T is gone. That's not magic library code by the way, if we make our own:
pub fn vanish<T>(_x: T) {}
Now we can call our vanish function and the same happens.
So yeah, automatic memory management.
As with the rest of the language, the automatic memory mangement requires that your program has no mistakes - which requires inhuman amounts of care during implementation and testing. So, that's obviously a spectacularly bad idea, but it's still automatic memory management.
What drew people to a lot of your examples (Javascript, PHP especially) was the runtimes rather than the language features. Your example sets aren't just demarcated by Smalltalk-ishness/ML-ness but more by runtime type (Scala & Kotlin are odd examples given the VM but for the most part your latter examples have build-time compilation while the former have plaintext interpreters).
We're definitely in a tools-heavy era of programming where, unless you're writing bash scripts, even very basic applications in interpreted languages are layered with a slew of transpilation/compilation/etc., but generally speaking I still don't see the majority of people moving wholesale away from plaintext runtime interpreters. Where are the ML-ish competitors in that space?
The nitpicky take: a lot of these languages come with a repl / console. E.g. the most recent version of Scala builds Scala CLI[1] into the language. You can run `scala repl` and just type code into it, or run `scala SomeFile.scala` and it will compile and run `SomeFile.scala`. There is special syntax for writing dependencies so that a single file can pull in the libraries it needs.
The 5head thought leader take: the traditional model for typed languages has two phases: compile time and run time. Types exist at compile time. This is inadequate for many applications, particularly interactive ones. E.g. a data scientist doesn't know the shape (type) of the data until it is loaded. It should be possible to infer the type from the data once it is loaded and make that type available to the rest of the program. We know how to do this (it's called staging) but it's just not available in the vast majority of languages. Staging, and metaprogramming in general, is perhaps the next great innovation in programming languages (which will take us from ML to Lisp).
In general, the challenge for these new languages is to reach "down" into the simpler scriptier applications, instead of the "serious" programming they are usually built for.
In Rust Vec<Option<Infallible>> is just a counter. Really, that's not theory that'll just happen by default because of how type arithmetic works.
In C++ you can't even write down an equivalent type, let alone say how it would be represented, the type system blows up long before we get there.
Separately `Vec<T>`, if `T` is a zero-sized type, never allocates and has a capacity of usize::MAX. Then, when you push into the Vec, because the value you push is zero-sized, there's no allocation and no data to write anywhere. Therefore the only effect is to increment the length counter.
1) lets dispense with the Allocator, for a typical Vec the global Allocator is used, so this type has no size, every Vec is talking about the same global Allocator, its state is tracked internally to itself. We need not consider this object further†
2) now lets dispense with that unique pointer. Its semantics are crucial if T had non zero size because this is why Rust knows the associated memory which is pointed to is "owned" by the Vec. However, for a zero size T this pointer is entirely unused.
3) Capacity at last actually is used, it's a machine word sized integer, so on a modern computer that's 64-bits, 8 bytes to store the capacity of the Vec, which will be the maximum possible unsigned integer of that kind, usize::MAX. It is set to this value when the Vec is created (because the size of T was zero) and never changes.
4) Length is also used, despite not needing to store any data for T the Vec is finite and this tracks how many of the zero size item are in the Vec. Thus, it's a counter.
† In C++ they use the "Empty Base Optimisation" to avoid needing space for such things, in Rust their size is just Zero and so they won't be stored.
What is the use case? Vec<T> is a generic type (Rust's generic growable array of T) so although Vec<Option<Infallible>> seems somewhat useless as a concrete type, it is likely to sometimes occur in generic code.
Example: generic code to do a bunch of potentially fallible operations and remember whether and how they failed for later summarisation may make a type Vec<Option<E>> where E is the failure type. When the operation wasn't actually fallible E is Infallible and instead of an actual growable array type we're just making a trivial counter, our summary is going to inevitably say that all N operations were successful, any code for the "List of failures" summary should even get trimmed as dead code in this case since it depends on an Infallible object and the compiler knows Infallible cannot exist.
There seems to be slightly more compile time work done in Rust but using the use case you described the concept of a Vec<Option<Infallible>> can just as easily be represented as std::vector<std::nullopt_t> with very similar semantics although possibly slightly less optimisation, although I would think if it was actually used in the language extensively I would see library implementors makeing it entirely compile time just like in rust.
I don't disagree that algebraic data types are nice and that Rust has an interesting implementation of them, however this specific example doesn't seem unrepresentable in C++ using std::option and std::variant although the actual semantics relating to usage is easier in Rust.
pub enum Foo {
Foo,
Bar(A),
Baz(B),
Qux(C),
}
pub enum A {}
pub enum B {}
pub enum C {}
The optimization applied to Vec also applies to any zero-sized type.I spent some time trying this out in Godbolt†, and then by hand, and then reading the Microsoft STL, and exactly as I'd assumed before I read your comment it was a growable array of one byte objects, with the type std::nullopt_t, 400 objects? 400 bytes. 4 million objects? 4 million bytes.
† Including longer than I'd like to admit forgetting that C++ silently copies large objects without telling you and so my diagnostic was actually making a new std::vector because I forgot to write an ampersand to make it a reference...
It is still possible I missed something and so I invite you to tell me what that was. But overall my thought is that you've missed the whole point and are in the same place as when C++ programmers get confused and think Empty types (like Infallible) and Zero Size Types (like Option<Infallible>) are somehow the same, which is like confusing zero (The additive identity) with one (the multiplicative identity).
It's pretty clear that we're talking about very broad families, which is okay—Rust has more in common with ML than it does with Smalltalk or ALGOL (though the ALGOL heritage is definitely present).
https://saityi.github.io/sml-tour/tour/02-09-mutable-refs.ht...
(OCaml has something similar too.)
>Rust is the first language to bring non-GC automatic memory management to the mainstream. . . . Other languages in this space include . . . OCaml[1]
But your own link[1] says,
>The OCaml compiler does not statically track lifetimes. Instead, it relies on a garbage collector to figure out a suitable lifespan for each value at runtime. Values are collected only after they become unreferenced, so OCaml programs are memory-safe. To a first approximation, this model requires allocating all values on the heap. Fortunately, OCaml’s generational GC can efficiently handle . . .
Scala does run-time garbage collecting, too (or rather the JVM does, which Scala depends on at run-time) unless I'm very much mistaken.
Scala is quite a bit older than the others, I'd put it in a previous generation.
I always saw Kotlin as a more convenient Java, without Scalas heavy tilt towards FP.
Java also added the equivalent of sum types with sealed interfaces and exhaustive pattern matching, does that make Java an ML language?
Many older languages by now have incorporated valuable parts of FP/ML (including Javascript, Java, C++, ...)
Borrowing some concepts from ML doesn't put these languages in the ML family.
> Java also added the equivalent of sum types with sealed interfaces and exhaustive pattern matching, does that make Java an ML language?
I don't think so. Java heavily relies on mutation and you can see this throughout the whole ecosystem and even the JVM whereas OCaml doesn't. Kotlin also relies on mutation (it relies on Java's stdlib after all) whereas Scala has its own stdlib with both mutable and immutable classes but defaulting to immutability almost everywhere.
So if anything, Scala can be called an ML language imho.
not really, the type-system-sublanguage is still modelled like classy OOP, not more like MLy modules.
When people bitch about the borrow checker, what they usually mean is “std::move is a great wristwatch but god damn does it chafe as a chastity belt”.
I don’t mean to be obtuse but I don’t see what that has to do with linear typing as a fucking weird mandatory default?
Rust was shaped by Mozilla wanting a language to replace parts of Firefox with. A language that is low-level enough to give very high performance, enforces correctness through the type system, and yet has many abstractions so it feels modern and convenient to use.
Rust doesn't have any automatic allocations. You can wrap every type in `Arc<Mutex<T>>` and treat it like a very verbose Python, but that's the programmers choice.
The design decisions have been validated by the success of Rust in certain domains.
There is a reason why more than one programming language exists...
If it wasn’t actively seeking world domination I’d be like, cool let’s use that sometimes.
Rust is so hell bent on a Rust monoculture ranging from stuff like TRACTOR to actively opposing interop that a few of us who know our shit have to be like “easy now” once in a while.
I’m trying to decide between tiktoken and sentencepiece for a new vocabulary at the moment, and it would be easier in some ways to go with tiktoken.
But I want it to run faster than a fart at inference time, which means I’m linking libtorch, which means I’m writing C++.
And fuck, link to Rust from C++? Let’s take another look at the Google stuff.
Hylo specifically says it intends "world domination" (next year in fact, having apparently completed all the delayed 2023 goals and also all its 2024 goals this year, I guess maybe they're going to do it all in December?)
But I don't really see this from Rust. I'm sure Hylo's "world domination" is a joke, but, so is the "Rust Evangelism Strike Force". Rust people I've interacted with strike me as very much more accepting of the "better tool" theory than of some weird cult or panacea. When somebody in a Rust forum says (e.g. about JPEG XL) "Ooh we should use Rust" and I say "No, WUFFS. This problem is what WUFFS is for" the responses tend to be a mix of "Yeah, I guess" and "I didn't know that existed. Thanks for the link" rather than cultist denial.
I admire both the technical sophistication and passion you folks bring to the table, the one true language thing just isn’t my bag. I pine for the Halcyon days when HN was run by the Rust people rather than the LLM idiots.
Keep doing what you’re doing, one hacker to another.
Rust just uses the C ABI for interop (including interop with Rust code that might not be part of the same build, such as dynamically-linked program objects). So you just have to come up with a plain C API for the interface and write wrappers on both sides of the divide that reference the C API. There are crates that will help with this, even achieving something like a "stable ABI" for Rust interop.
(And we'll probably see some work into interop with Swift itself (which has a stable ABI of its own) once there's enough interest in that as a memory safe language.)
But bindgen/cbindgen/cxx/etc exist for a reason: it hurts like a root canal.
In my particular case, tiktoken is substantially like 600 lines of Rust. If I have a deadline do I fuck with the linker or just write it again? I’m marshaling std::string here, fuck let’s just write it.
What I’m not going to re-write because I’m not a magnificent maniac like geohotz? I’m not going to rewrite libtorch. Which is written in C++ like all the software you can’t really live without.
The laziness as a default just makes boxing closure values a sensible default, when the type system requires the boxing of everything in general.
Parametric polymorphism is kind of orthogonal to that. It’s related I guess. It can involve some kind of runtime dispatch, but usually that gets JITed or inlined or whatever unless you really insult the compiler.
And even full frontal invokedynamic it’s still like, is it in the BTB and the I-cache? Ok well we’re still doing business.
Now GHC does stack allocate, but it’s hard to count on without really aggressive hinting.
An `async {}` block is not evaluated, but converted into a generator/state machine that lives on the stack, and that has to be advanced by calling a poll method. You can move it to the heap , but that does not happen automatically.
Sharing of values is obviously much more awkward due to mutability and no automatic cloning though.
But to be clear, anyone with the username the_duke is probably really cool, and you clearly know your stuff as well as having style, so count me as a fan.
But not so much in the conditional branch sense of the phrase “control flow”.
More like, I’ve got a bunch of references and one of them is a reference to a function I might evaluate, damn, I need that reference and the transitive closure of everything it knows about, all of which are probably on the heap.
A space leak, much like any memory leak is when you or the compiler or the runtime or whoever misplaces such reference.
GHC having a closure in scope in no way immediately means there is a space leak. Plenty of Haskell code that compiles to thunks will run all day in an amortized fixed-size heap.
Much like Erlang, which is what Joe Armstrong had designed before he said the thing about jungles you’ve quoted.
And it is in no way difficult or even really rare to drop an Arc in a table and leak all over the place.
Rust has all the same computer science problems as anything else. Full stop.
I like a lot of things about Rust: it’s got compiler checked exhaustive pattern match and a good initialization syntax and Either baked in and fucking correct package management and a ton of other things.
I’ll look at Clean to see if linear-only memory management can be done gracefully. Rust is a lot of cool things but not the language that demonstrated that.
I'd argue that Erlang (with gen_servers being "objects") has more in common with Smalltalk than these.
One of these is not like the others. Javascript derives from Lisp (first-class functions, lambdas, closures), not Smalltalk.
Or maybe you think of the fact that JS uses prototype-based OOP, which makes it closer to what one would do with Scheme (something inspired from CLOS I guess) than Smalltalk ?
CLOS is an evolution from Flavors and similar Lisp packages, and outside Lisp, Julia would be the closest to it, in modern times, not counting Dylan.
Isn't prototype-based OOP roughly a decade older? -> for example "Director" from Ken Kahn is from 1976.
Although Wikipedia also points out that, https://en.wikipedia.org/wiki/Prototype-based_programming
Actually your remark has made me dive into alternative prototype based languages.
Director Guide: https://dspace.mit.edu/bitstream/handle/1721.1/6302/AIM-482b...
Creation of Computer Animation from Story Descriptions: https://dspace.mit.edu/bitstream/handle/1721.1/16012/0720063...
The other mentioned tool was Thinglab, by Alan Borning
https://www.ijcai.org/Proceedings/77-1/Papers/085.pdf
"The Smalltalk notion of an object has been modified and extended in a number of ways. In ThingLab, objects are constructed interactively by editing and making descendants of prototypes. An abstraction hierarchy is used that allows arbitrarily many levels of both parents and descendants, with the problem of objects with several parents being handled by the technique of merging. There is no distinction between classes and instances."
"The Programming Language Aspects of ThingLab, a Constraint-Oriented Simulation Laboratory" https://dl.acm.org/doi/pdf/10.1145/357146.357147
"CLASSES VERSUS PROTOTYPES IN OBJECT-ORIENTED LANGUAGES" https://dl.acm.org/doi/pdf/10.5555/324493.324538
Available in the Browser, running in Smalltalk: https://constraints.cs.washington.edu/thinglab/
If you want non-default safety you do not need to leave C++. That is why unlike all these new languages, Rust makes difference.
1. A large part of why many people love Rust is that it's the first time they've used an ML family language. One of the innovations of Rust was to create a community that felt like home to Unix hackers who weren't programming language nerds.
2. Rust is the first language to bring non-GC automatic memory management to the mainstream. It won't be the last and it might well be the worst. Other languages in this space include Swift (as in OP's post), OCaml[1], and Scala[2]. These alternatives take the approach that tracking ownership is not the default, which is fine for the majority of programs and far more ergonomic.
There's also a corollary
3. The age of Smalltalk is over. It's now the age of ML. (Amusingly, going backwards in time, from 1983 to 1975). The languages that dominated the 2000s (Ruby, Python, Javscript, PHP) were all more or less derived from Smalltalk (everything is an object! dynamic types! runtime metaprogramming!) The new languages (Rust, Scala, Swift, Kotlin, etc.) are ML family languages. Similarly, back in the day you could learn, say, Ruby, and still be reasonably proficient in Python or JS. Now you can learn, say, Scala, and pick up Rust or Swift reasonably easily.
[1]: https://blog.janestreet.com/oxidizing-ocaml-locality/
[2]: https://dl.acm.org/doi/full/10.1145/3618003