Untangling Lifetimes: The Arena Allocator(rfleury.com) |
Untangling Lifetimes: The Arena Allocator(rfleury.com) |
Also the author's condescension about automatic memory management is... telling. Remembering to call "free" is only one small part of why tools like RAII are good, and arena allocators do not help with the other parts.
The overall goal is to be able to write correct, reliable, performant software. Arena allocators help prevent missed or double frees, but they don't help with the other memory safety issues. For example, sometimes objects in different arenas need to reference each other and C does not help prevent you from accessing those references after one of the arenas has been freed.
On top of that there are general issues with arena allocators:
1. They can be inefficient for resizable collections where you don't know the total length in advance. The multiple re-allocations results in a lot of wasted space in the arena.
2. In C, it's implicit which objects should belong to which arena.
3. It's not composable. A library doesn't necessarily know which objects should use which arena, or may not even support arena allocation at all.
4. There are other resources than just memory. There's a reason it's called RAII and not MAII - because it allows you to clean up all kinds of resources (eg. file handles) when an arena allocator doesn't typically support any kind of destructor.
5. It requires more thought to structure your program in this way. For some programs that may be effort well spent, but a lot of programs are not bound by allocation performance, and for those programs not thinking about allocation at all leaves more time for thinking about correctness in other aspects.
6. Languages which do automatic memory management can still support arenas, and may offer additional benefits when they do (eg. Rust's explicit lifetimes can tie an object to the arena it came from).
7. The stack and heap are global resources that most code can simply assume exist, and use with no extra ceremony. When you have arenas in play, these need to be passed as additional arguments. Adding a new allocation to a function can require sweeping changes to add a new arena parameter to every function above it in the call stack.
4. Nothing prevents arena allocators to track other resources than memory.
7. This is not a big problem in practice from my experience. You can just do like Zig and always pass an allocator or you can use global/thread local variables like PostgreSQL.
But I also would like to add my biggest issue with arena allocators: they do nothing against use after free.
> 6. Languages which do automatic memory management can still support arenas, and may offer additional benefits when they do (eg. Rust's explicit lifetimes can tie an object to the arena it came from).
Languages with automatic memory management don't expose the arenas as an implementation detail, and so they are free to work out how to compose them, and when to change their minds.
Java has had copying collectors practically from the beginning, and that ability to rewrite pointers after the fact affords you the ability to work with heuristics that have known failure modes, where any mistake becomes a use-after-free bug. Moving an object out of a volatile arena is just an inconvenience. So it evolved from generational collectors, to thread local arenas, to thread local arenas where some objects that are likely to survive are never allocated into the arena but 'pre-tenured'. This process really hit its stride with the introduction of escape analysis, which is a logical ancestor of today's borrow checker. With escape analysis you know for certain some objects never escape, in which case you can even inline simple objects (eg, register allocation). And if an arena has an escape probability of 0, there's less guard logic to deal with, and there is some short circuiting one can do when considering the root set for Mark and Sweep.
You might prefer a public, conversational setting at the conference instead of a personal blog post. That said the article made useful technical points. And he wasn't hurling expletives or denigrating any individual.
I hope you can appreciate that there’s room between “technically correct” and “hurling expletives” for some someone to simply dislike an article. I regret my response struck a nerve—it certainly was not meant as an attack on its author.
again: nothing like this was ever presented to us, except as some sage wisdom from upperclassmen who had figured it out on their own. you learn malloc/free when you learn C, then you learn new/delete/RAII when you learn C++, and you get a brief overview of how garbage collection broadly works in higher-level languages, and that's it, that's the education about memory management. I had never explicitly heard of the concept of a "lifetime" until Rust started gaining traction. it wasn't until working on my sophomore year game engine that an upperclassman explained the idea of building a memory-managing array-like data structure that, instead of reallocating when the capacity is reached, instead leaves the existing items where they were and allocates a new "page" of memory, so as to ensure pointers to existing items remain functional.
hell, I'd never even been exposed to the idea of a per-frame "scratch" pool allocator until I saw Jon Blow talk about having such a thing as a built-in feature of his new programming language. once I saw how it worked, I was floored—it's such an easy-to-explain concept, infinitely useful, basically gives you "garbage collection for free" for any number of one-off things you need allocations for in a given frame of a video game (or other interactive application)—without the need for Actual Garbage Collection!
I agree that this article is a bit lengthy, but in my opinion it does a great job of setting up the knowledge necessary to understand what it's talking about, so I'm not really sure how I'd do a better job. what in particular did you find condescending about it, especially within the first fifth of the article?
---
[0] from what I've heard, this from-scratch game-engine-creation is now gone from the curriculum entirely, and now students just make shit in unreal or unity, which is tragic, imo.
Here's that Jonathan Blow stream if anyone else is curious: https://www.youtube.com/watch?v=ciGQCP6HgqI&t=130s
It's impossible for me not to read this without imagining a sneering tone.
And a few paragraphs later:
"As you may be able to tell from my tone, I think this way of thinking is nonsensical."
As for your post, it's hard for me to get worked up about whatever the game programming curriculum is these days. There are more important things in the world. Good to keep things in perspective: it's school... for game programming.
For example, if you're decoding a certificate, or maybe something larger and more complex, a decoder might malloc() every little thing as it goes, which then necessitates free()ing each of those things when you are done with the whole decoded thing. But if you can have the decoder allocate from an arena, then when you're done using the decoded object you can just free the arena.
The decoding example is very common. Whether it's JSON, XML, ASN.1/DER/whatever, Protocol Buffers, Flat Buffers, or anything else, it is very common for decoders to create a ton of garbage to collect. Optimizing that garbage collection seems like a useful thing to do, but it's hard to do in a memory-safe language because every reference to a sub-object of the decoded object will need to be dropped in order for the object's arena to be released. How would one handle this in Rust, C++, or Java?
The first big difference is that it works with non-memory resources too, for example open/close for files.
The second is that it makes heap allocated data feel a lot like stack allocated data, since the resources are ultimately owned by variables on the stack. With “move semantics” resources can be passed as arguments and returned by functions too, so the example of lifetimes crossing calls is fine. A function could take in a File, read some data, use that to select and open a different File, then return the new file and close the old file at the end of the call, and it would all be straightforward.
This is all present in C++. If it seems like an interesting idea to learn more about, I recommend trying it in Rust. It is the default, so there’s minimal syntax for it and all the standard library uses it.
https://ziglang.org/documentation/0.9.1/#Choosing-an-Allocat...
There is even an arena allocator provided (amongst others) by the std lib.
https://www.rfleury.com/p/untangling-lifetimes-the-arena-all...
UAF would be the same difficulty as allocated arrays I would think?
The point is not perfection, it is simplicity and mapping tools to requirements.
Does anyone know what would be the alternatives to VirtualAlloc on other platforms?
The big question about all that is when this memory is actually being released back to the OS, because by default it isn't really. That also means you can trigger OOM faults despite the process having free memory even (which the OS sees differently).
Nobody has ever claimed this, ever, making this a major strawman. Does the author also consider everyone a childish padded-room pussy if they like seatbelts in cars and safety-related infrastructure on highways?
The intro to an article should be to engage the reader and get them invested in your topic. Straw-manning memory-managed languages as something designed only for weak-minded children does quite the opposite of this.
> an interface and its implementation are intrinsically related in subtle ways.
Yes, they are linked in that the implementation is constrained by its interface.
> when the nature of the implementation must change, the interface must also fundamentally change
The author wildly misunderstands interfaces and abstraction here. Interfaces are not for the implementer! They are entirely for the consumer! Interfaces change when the requirements of the consumers of that interface change, not when its implementations change.
Although honestly I have no idea what this has to do with his main point: malloc/free aren't changing, so ... ?
> Another attempted solution is garbage collection, which is a large enforcement structure that tracks everything and interrupts productive work in order to perform its function (much like a government agency, except in this case, the garbage collector is ostensibly doing something approximating useful work—although both function by stealing valuable resources involuntarily).
Tip for the author: if you're trying to convince me of something, you may want to avoid sending out "I am an narrow-minded asshole who understands nothing and is angry about everything" signals. It makes me think whatever you're trying to convince me of is only for angry, narrow-minded assholes who understand nothing and are angry about everything.
> modern programming thinking (and education) ... claims many problems are gross and complex, and thus we need abstraction to make them appear simpler.
But not our Great Prophet Author. He knows the world is a Simple Place where Government Bad and Garbage Collection Bad and Universities Bad and Everyone Else Stupid and Everything Is Easy If You're Not An Idiot and Nothing Changes and Users Are Stupid and Programmers Are Stupid and Educators Are Stupid and everyone's just making everything way too hard and if only people would listen to his rants then they'd stop being so stupid.
(And fundamentally misunderstanding abstraction, again).
I really want to know what Arena Allocators are. I've never heard of them. They sound cool. But this is one of the most arrogant, narrow-minded, condescending, ignorant authors I've seen posted on Hacker News. I guess I'll read about it elsewhere. And this angry asshole is asking for subscriptions!?
I disagree. One, I think the set of C code where both "allocates enough pointers that calling 'free' is too complicated" and "memory leaks are not a problem due to short runtime/low memory usage" is very small. Two, having a pattern where calling free is too complicated might indicate problems with the code: if your code makes it hard to manage lifecycles, I bet it makes several other things harder than they should be. Three, one should think what happens when that code is used in other places: writing proper lifecycle management at the beginning is easier than doing it when it's used as a library.
> This simplifies all codepaths in this system. The parsing code becomes simpler, because it does not have to have any cleanup code whatsoever. The calling code becomes simpler, because it does not have to manage the lifetime of the parsed tree independently. And finally, the allocator code itself remains nearly trivial, and lightning fast.
Again, disagree. The calling code becomes more complex because now you have to link the arena to the object it creates. It's very easy to make the mistake of passing the object without the associated arena, then releasing the arena, and boom the object is now garbage. And, as I saw in another comment, cleanup might involve more things than just "freeing memory", so you might end up iterating all the structures nevertheless. On the other hand, with RAII and smart pointers (or GC, or refcounting), you don't have to manually iterate through the structure and free and do cleanup. When things go out of scope, they get cleaned up and deallocated, and that's it.
But the main issue is memory fragmentation and overallocation. Precisely the example of a JSON tree, where possibly multiple conversions are done and auxiliary structures are allocated, is where I think not all things will be allocated in a perfectly linear fashion, and therefore freeing in a stack allocator will lead to a lot of unreachable memory inside of the stack. I find it surprising that a post that talks about arena allocators doesn't even mention "fragmentation".
Are arenas a good tool? Yes, and like every good tool, they have advantages and pitfalls. This post makes it look like they're better than RAII and garbage collectors, and that's false. And the main problem isn't that it does so by virtue of having different opinions, but by hiding the pitfalls of arena allocators.
Svelte and SolidJS. They add a compilation step that turns fully abstracted SPA framework code into vanilla JS doing efficient DOM operations. If you wanted something even closer to the speed of C, you could imagine a framework using WASM to generate arbitrarily complex raw HTML and add it to the DOM in a single operation. This would basically match the speed of plain old SSR, with only the limited overhead of running WASM on the client.
In Rust you'd prefer to write the parser to slice the original memory and not copy out until you're done parsing. You can see this in, for instance, the signature of methods in the httparse crate:
pub fn parse_headers<'b: 'h, 'h>(
src: &'b [u8],
dst: &'h mut [Header<'b>]
) -> Result<(usize, &'h [Header<'b>])>
To translate, this means that you must provide a byte slice that lives at least as long as 'b, and a mutable array of Headers that lives at least as long as 'h, and those headers then may reference data that lives as long as 'b (that is, the original bytes).This way we avoid creating the garbage in the first place by demanding that the original allocation live long enough.
I gather Rust is beginning to accumulate similar accommodations. It is already explicitly "safe" to seem to leak memory.
~Foo() = delete;
Or be trivial (https://en.cppreference.com/w/cpp/language/destructor#Trivia...), then use compile time checks on std::is_trivially_destructible || !std::is_destructible
to be allowed into the arena.That's worse naming, not better!
In particular, a copy does not change the thing it copies - but a C++ move does change the thing it moves from.
My question is: what makes this so useful? Looking at wikipedia, the focus seems to be on lifetime guarantees, but I'm having trouble understanding how these are useful in practice. Contrasting this with a non-RAII language like Go might be helpful.
Primary purpose: To guarantee the cleanup of resources during stack unwinding, after an exception is thrown, because it's tightly bound to the automatic storage duration mechanism (the static code generated to manage the stack at runtime).
Secondary purposes: 1) To immediately initialize some resource handler where it is declared. 2) To automate the cleanup of some resource when execution leaves the scope in which it was declared, also hiding explicit calls to Close, Free, Unlock, Release, etc.
To restate the source, RAII was the premise for exceptions. [1]
[1] C++ in Constrained Environments - Bjarne Stroustrup - CppCon 2022
It's not about initialization. It's about cleanup. The initialization aspect of RAII is incidental to its main purpose/use -- perhaps essential to it too, but not so essential that initialization becomes more the essence of RAII than cleanup.
It's really a C++ specific form of Lisp's unwind-protect.
Not really. The idea is that any resource lifecycle management should be tied to the lifecycle of an object, therefore removing a whole class of problems. For example, when you open a file handle, the "open" operation should be tied to a class constructor, and the corresponding cleanup to the destructor. If you lock a mutex, that operation should be handled by a constructor, and the release by a destructor, so that you never forget to do the cleanup.
> My question is: what makes this so useful? Looking at wikipedia, the focus seems to be on lifetime guarantees, but I'm having trouble understanding how these are useful in practice.
I think Wikipedia should have an example of the things that RAII makes you avoid. Compare these two designs
class Config {
Config() { this->file = nullptr; }
void load(const std::string& path) { this->file = open(path); }
void close() { this->file->close(); }
versus class Config {
Config(const std::string& path) { this->file = open(path); }
~Config() { this->file->close(); }
The first design makes it easy to forget calling close() before releasing the object, or maybe writing a destructor that does a double free if close() had already been called... The second design is clear, and also safe to use around exceptions.As for the comparison with another language... I don't know enough about Go, but RAII is present in a lot of object oriented languages. For example, in Python, opening a file creates a "file" object with the resource (the file handle) already allocated, and when that object refcount goes to zero, the underlying handle is released. However, because in modern languages all the "resource management" is usually done in standard libraries and memory allocation isn't done manually, programmers of those won't really hear about RAII, because all that is managed already in the standard libraries.
f = os.Open(...)
defer f.Close()
// do stuff with f...
This ensures you close the file at the end of the scope. If processing the file is complex, with lots of places where return is called, then there's lots of places where the Close call could actually need happen. Thankfully you just write it once with defer, and you can forget about it.In languages without defer you need something else. The RIIA approach is to create a file object where the constructor opens the file and the destructor closes the file. The compiler needs to track the file objects scope in order to call the destructor. This acts as a hook to run some code right at the end of the scope, wherever that actually occurs.
Both call close right before returning, but the actual mechanism is different because the languages have different tools.
The result is that you are essentially trapped into the realm of manual resource management, having to put a try block around every single use of a file to make sure you can control its lifetime and prevent it from "leaking" into the collector. You then would have to use a finally block and call close. Memory is certainly the easier problem as it is a single constrained resource with usage semantics (licenses and conflicts) entirely constrained by the language.
(They at least have recently added some syntax that makes the last bit of that easier, but which doesn't at all make it less manual. This syntax is somewhat based on C#'s using blocks, a feature which I might have actually caused due to some strong advocacy surrounding this issue when .NET was in beta, and yet I have always insisted this syntax failed to correctly understand the problem as, even if you buy into the manual-ness of it all, it leads to colored objects, as there are objects controlled by the garbage collector and objects controlled by using and now adding a field to an object that must be disposed flips it from one regime into the other regime without causing a type incompatibility.)
And it works.
Resource life management is not a problem that Java handles with grace...
c.f.) Java, where the GC can handle memory, but it didn’t work out so well for files, so, to restore correctness, we needed the Closeable and AutoCloseable interfaces to give manual control back to the programmers.
> But not our Great Prophet Author. He knows the world is a Simple Place where Government Bad and Garbage Collection Bad and Universities Bad and Everyone Else Stupid and Everything Is Easy If You're Not An Idiot and Nothing Changes and Users Are Stupid and Programmers Are Stupid and Educators Are Stupid and everyone's just making everything way too hard and if only people would listen to his rants then they'd stop being so stupid.
Yes.
> And this angry asshole is asking for subscriptions!?
Yes. (and people subscribe)
But you go on intentionally filtering out professional software engineers with over a decade of experience if they have even the slightest difference of opinion about the subjects you write about. Clearly they have nothing to offer you: you already know everything anyway.
Here's an example where in a commit I swapped in an arena:
https://github.com/williamcotton/express-c/commit/4ae53f38e3...
* A lot of arguing that all other ways of dealing with the problem (garbage collectors, RAII, etc) are misguided and make for worse programs. This is a lot more controversial than "arena allocation is useful" but I have some sympathy for it. He's not alone in thinking it. See the "Handmade Network" which this guy is associated with.
* A brief and unnecessary bit of free market fundamentalist politics, which seems par for the course for him.
* A general implication that he came up with this stuff himself, which he didn't. Arena allocation (under that name) dates back to the sixties.
Also:
>Learning how to work with arenas entirely revolutionized my experience with writing code in C.
But, right, arena allocator is a tool, not a solution, you still have to invent a solution every time, it's still manual memory management, just an easier one.
How in the world did you get this from the article? Of course I didn't invent it, nor did I ever claim (or imply) that.
Malloc/free - can turn into a tangled mess as you need to keep symmetry, and apps might have complex dep. graphs
Stack - wonderfully simple but often if you need to share things across the depths of your program you need to define variables in shallower parts - but you won’t know how much is needed ahead of time. Example a parsing library.
Arena - like a single malloc / free with it’s own stack. Use this to allocate memory for objects that can be all freed at the same time (if they need to be freed at all). No need to malloc for each object.
I think the idea is a really interesting one but it would be better if he didn't wrap it in typical "I don't make mistakes" hubris. I mean I guess at least this time he has some more reason than "because I'm not stupid" but I think he's still wrong.
His technique probably reduces the chance of memory errors, but there's still nothing checking his work so he's still going to make mistakes.
The other issue he doesn't address is resource allocation. It's fine to just drop the memory of a lot of things but sometimes you have to close files, handles, etc. RAII handles that perfectly. I'm not sure about this.
I would say RAII and Rust's borrow checker are still superior but it's definitely true that they don't work well with arenas.
If you're asking whether arena allocators can handle resource cleanup, then I think the answer is yes. One way to do it is to embed an intrusive linked list of destructors into your arena. So, to push an object onto the arena you would:
1. Push a function pointer (the object's destructor) and the current destructor list head onto the arena.
2. Update the destructor list head to point to the top of the arena.
3. Push the object's data onto the arena, as usual.
(source: https://www.ea.com/frostbite/news/scope-stack-allocation)
A downside of RAII is the hidden cost of unwinding, and predictability over CPU usage. If you have a resource that supports complicated internal transformations, and memory states, then you need an equally complicated destructor that can handle all those different states. If you're not careful with this you can get CPU spikes at scope-exits.
I think there is a fundamental difference between memory and most other resources: (process's) memory is a) effectively an internal detail while other resources are externally visible (open files/sockets/etc); b) as almost a consequence, it's pretty much anonymous: when you ask for 1 MiB of memory, you ask for free, unused memory; when you ask for file "/etc/passwd" or TCP port 8080, you ask for that exact file/port.
That's why you need special destructor calls for non-memory resources, to tell the external environment that this resource is now available for others to use. Memory, on the other hand... the OS with virtual memory support will generally be able to dispense free memory until the swap file consumes all of the available disk space, and even then, it could theoretically just start dropping pieces of swap (and when a process actually tries to access the gone memory, kill it with SIGSEGV) to prolong the agony. Throw in the most rudimentary form of in-process memory reclamation, and you won't notice memory leaks until well into months of constant uptime.
Note that this will definitely improve in future versions of Rust. Local allocators are an unstable feature already, and can support arena allocation.
All of my formal instruction in C never mentioned this middle ground and it took both trial and error and working experience to learn about memory arenas.
Also, I’m convinced that evangelizing Rust memory safety has probably done more harm than good because to outsiders it is being made to seem like anything done in C is wrong and stupid and therefore no one should pay any attention to anything that has ever been written in C.
One of my worries before I start working in Rust is that I won’t be able to use my knowledge of resource management in C. That’s not the case at all but it takes having to filter through flame wars to figure things out!
Memory allocated with mmap is released with munmap.
brk/sbrk can increase the size of the data segment, thus allocating memory, but they can also decrease the size of the data segment, freeing the memory.
Whether calling the function free of the standard C library results sometimes in also invoking munmap or brk/sbrk to release memory to the operating system is obviously implementation dependent.
When malloc/free are bypassed and you get memory from the OS directly with mmap, to be used by a custom memory allocator, e.g. an arena allocator, then it is up to you to call munmap when the memory is no longer needed.
> In this post, I’ll be presenting an alternative to the traditional strategy of manual memory management that I’ve had success with: the arena allocator.
and
> My approach, on the other hand, is this: instead of assuming that malloc and free were the correct low-level operations, we can change the memory allocation interface...
Together with a complete absence of any reference to the history of the technique or any of the people besides you involved in its development.
It's not an accusation, but it's a clarification worth making as I genuinely think someone without knowledge of the subject could come away thinking you were presenting your own innovation.
I definitely don't know everything, and I am always learning from real professionals with real experience. But it's certainly true that you don't have anything to offer to me.
And that's why automated memory management (garbage collection) is a success story while attempts to blindly apply it to other resources failed: memory is different. Freeing other resources sends a very clear message to the external environment, including other processes, which is used for IPC while freeing memory doesn't.
Yes, in the end, memory in process is implemented by (shared) physical memory but modern OSes go to ridiculous length to support the illusion that it's unlimited and private. Yes, there is shared memory mappings which I explicitly leave outside the argument: those are externally visible, nameable, and hard to manage with GC.
Okay. Yeah, that doesn't seem great.
> There are more important things in the world. Good to keep things in perspective: it's school... for game programming.
Wait a second. Isn't your own statement kind of hard to read without a sneering tone?
In defense of videogames. It's only a massive industry that drives technological developments massively useful in other industries; while at the same time, providing entertainment, recreation, and escape for billions of people.
In defense of complaining about sneering tones while having one yourself: <space intentionally left blank>
> Education around memory management—as it was presented to me—was purely a historical, academic endeavor. How did the Linux kernel originally do memory management? Let’s do an assignment on that subject so you can see how gross it is! Eww, look at that malloc! Weird! Oh, don’t forget to free it! But don’t worry, kiddo; in next class, you can return to your “safe” and “managed” padded-room languages where bugs and instabilities are “impossible” (or so they claim).
was your CS education appreciably different to what the author wrote above? is this not the increasingly-prevailing attitude among contemporary programmers?
> As for your post, it's hard for me to get worked up about whatever the game programming curriculum is these days. There are more important things in the world. Good to keep things in perspective: it's school... for game programming.
ok, should I refrain from posting unless I have something to say about world hunger or climate change or something?? hopefully you saw the point I was making which is that memory management techniques like those described in the article are pretty crucial for making high-performance real-time interactive applications, and hopefully you understand that not being taught about such things at a school whose whole point is to teach such things is pretty crazy—especially since, from the looks of things, these things aren't really being taught in school anywhere else, either!
do you disagree with the author about the importance of manual memory management techniques and theory? if not, then what other point were you trying to make with that last paragraph? your reply is very confusing to me.
---
EDIT:
> If you slow down
> Maybe because it's a relatively inconsequential topic (in the grand scheme of things) we can approach it more calmly and soberly instead of with a hyperventilating tone.
> I would also say that while it can be difficult for some people to approach tendentious issues without being combative, it is indeed possible. Some might even say it's a useful skill to spend some time picking up.
these are more condescending than anything in the article—projecting irrationality onto others is unbecoming.
> That said, whether or not I agree with the author isn't salient
it should be! discussions about programming techniques and methodologies are far more interesting and relevant than sentence after sentence explaining why you think the tone of the introduction of a lengthy article is Bad And Wrong, and, despite refusing to read the rest of said article, claiming that said article's length is also problematic.
Manual memory management is definitely an interesting topic of discussion, with important ramifications in the field of software engineering. Like you said, world hunger and climate change it is not. Maybe because it's a relatively inconsequential topic (in the grand scheme of things) we can approach it more calmly and soberly instead of with a hyperventilating tone.
I would also say that while it can be difficult for some people to approach tendentious issues without being combative, it is indeed possible. Some might even say it's a useful skill to spend some time picking up.
> "flat-copy-with-src-invalidation".
That's not what a move is in C++, at all. I wish we got destructive moves in C++, but we didn't (and I understand why..)
> for non-POD it's even slightly more expensive because you need to fix up the source.
This is just false.
As far as I remember from my 'active' C++ time, a move operator does a 'flat copy' of all 'top level items' in an object, and then puts the source object into a state where the destructor can safely be called on it without destroying any of the previously owned resources (which means for instance setting any owning pointers to zero so that the destructor doesn't free the memory that was owned by those pointers, because ownership of this memory has moved to the destination object). It gets a bit more non-obvious if any of those items are complex C++ objects themselves, but in the end it's always "copy all the bits, and then clear any owning pointers without freeing the underlying memory".
Did I miss something?
I’d like to start writing new projects in Rust and I’d like to continue to use this approach to memory management!
https://docs.rs/bumpalo/latest/bumpalo/#nightly-rust-allocat...
use bumpalo::Bump;
let bump = Bump::new();
let mut v = Vec::new_in(&bump);
v.push(0);
v.push(1);
v.push(2);