How Memory safety approaches speed up and slow down development velocity
verdagon.dev
verdagon.dev
The "leaky abstractions" argument doesn't work for me. E.g. in a Java API you don't commit (at the language level) to the ownership of passed-in or returned mutable objects. In Rust, you do, and the author claims this is a problem. I say that in fact you depend on the ownership of the referenced object either way; Rust forces you to write down what it is and enforce that the callers honor it, while in Java you cannot. Arguably therefore Rust is less leaky in that hidden assumptions are documented and enforced at the API layer.
I've had lots of good experiences refactoring Rust code. It's probably more typing than other languages but the increased assurance that you didn't break the code (especially in the presence of parallelism) more than makes up for it.
Yep, and so you get all sorts of bugs or defensive code everywhere. For that reason I also have a hard time with one section being
> GC'd code can be more correct
But apparently issues like iterator invalidation and other mutability conflicts don’t rate.
But OTOH not having to worry about defensive programming is so great. Nothing is NULL. Nothing will unexpectedly mutate. Thread-safety is explicit. It's always clear when vectors and objects are passed, copied, or only briefly inspected.
Java isn't the be all end all of GC based languages.
So then one ends up with the usual issue of guest languages, where one needs to create wrappers for the platform APIs to keep the code idiomatic and language invariants.
The same goes with Java: we never use mutability in modern code bases anymore unless we have strong reasons to do so and can keep that mutability as constrained locally as possible.
We also avoid inheritance these days, have your heard about that!? Because yeah, inheritance can be helpful but tends to bite you in the end.
We often think that the borrow checker protects us from these kinds of mutability conflict / single-threaded race conditions, by making it so we don't have a reference to an object while someone else might modify it. However, the classic workaround is to turn one of those references into an index/ID into a central collection. In real programming, that index/ID is equivalent to the reference. But while I hold that index/ID, the object can still change, which kind of throws the benefit into question.
In other situations where we only have multiple read-only references, there's not really a motivating problem to begin with, even when coded in other languages that don't also regard them as read-only.
I think the other benefits the article mentions (concurrency benefits, plus encouraging into flatter, cleaner architectures) are a bit more compelling.
They don't, because you're using java.util.concurrent when you need data structures that deal with concurrency.
I miss java.util.concurrent terribly when I have to program in any other language. I don't miss Java, but I weep every time I need the equivalent of ConcurrentHashMap or CopyOnWriteArrayList in another language.
Unfortunely people keep reading only about Java and then they think they know something about GC based languages.
Modula-3 and Mesa/Cedar are two examples from the past, D one from modern times, with many others in between.
This is a bit of hair splitting I admit.
Yep, splitting hairs. :)
D has GC, no GC ("@nogc" mode), or Ref-Counting (experimentally) with "@safe" and "@live" modes.
Provide all the required features to write a full OS, while having the convenience of a GC for most of the code.
This is unlike Java, which forces the use of GC and OOP. Or Haskell which forces functional style.
A lot of programs and large parts of a lot more programs just don't need these concepts at all and would run correctly with a GC that did not collect any garbage, memory permitting. Most Python code I write is in this genre.
On the flip side, at work we mainly work on a low-latency networked application that has per-coroutine arenas created once at startup and long-term state created once at startup. The arenas are used mostly for deserializing messages into native format to apply to the long-term state and serializing messages to wire format. There's not much benefit to be had from writing all the time that everything that isn't on the stack belongs to the per-coroutine arena, except for all instances of like 3 structs, which belong to the long-term state.
Most tools, even very technical tools like assemblers, debuggers and compilers, don't need deterministic memory management. Only operating systems, device drivers and some time-critical applications (like video/audio encoding/decoding) need deterministic memory management.
Nevertheless, after spending a year writing Rust, I'd take Rust over Java for just any use case now, even including CRUDs and GUIs (actually doing a GTK GUI for a Rust app right now, and it is not any worse in terms of dev speed than Java Swing was, despite what people say about callbacks - I really don't mind an occasional Rc/Refcell/clone here and there).
> Do you really need to know?
He, uh, just said that. If you're tracking profiling metrics like he is (lifetime of a call-stack, maybe?), you need to know.
> When you're interested in that sort of info you're using the wrong language
Under that argument, Java is the wrong language for everything.
If you have no idea how many resources are actually available, you are forced to use conservative strategies to protect the process. Guessing or ignoring resource availability is an excellent way to greatly increase performance variability and reduce throughput.
I think even within GC languages developer velocity varies a lot based on things like typing system, reflection, repl, and maybe interpreted vs compiled. My experience is that this variance is greater than what can be put at the feet of memory management
You'd love APL.
A quote from the GC Handbook: “modules should not have to know the rules of the memory management game played by other modules”
While Rust does make these refactors safe, it is nonetheless work that simply doesn’t have to be done in case of Java for example.
That's only the default, of course. Rc is commonly used in Rust to decouple ownership management from the simple use or passing of objects throughout the program, much like in any GC language. (Of course Rc on its own does not collect reference cycles; thus, "proper" tracing GC is now becoming available, with projects like Samsara https://redvice.org/2023/samsara-garbage-collector/ that try to leverage Rc for the most part, while efficiently tracing possibly-cyclical references.)
As for the cost it takes to release the resources, in MMM/borrow checking/refcounting this is typically bounded by the amount of stuff to release. In tracing GC it is bounded by the total amount of stuff on the heap, which is typically several orders of magnitude larger value.
In my book [0] teaching Rust, I manage to completely avoid using lifetimes. Perhaps this is because it reimplements a command line utility (head, wc, cat, ...) from the original BSD sources in each chapter and none of them are stateful. Most of the classic Unix utilities just process streams of bytes and barely use data structures at all. Many don't even call malloc().
It feels like Rust is a good match for the problem domain but I never made the connection with the stateless nature of these tools before. This bodes well for eventually replacing the coreutils with modern Rust implementations.
I dove a little deeper, and the stateful/stateless distinction seemed to be the best rule-of-thumb for predicting how much one would be in conflict with the borrow checker's preferred styles. It was also reminiscent of functional programming languages, in a way.
I suspect this is why most discussion comparing Rust to other languages devolves so quickly, and has become so polarized: it depends on the domain. Users who try to use it for some domains will hate it, and users who try it on other domains will love it.
This is also why I recommend newer Rust programmers to not be afraid of using Rc/RefCell in certain domains. Some domains don't agree as much with the borrow checker, and Rc/RefCell can make the architecture a lot looser and easier to work with.
The problem is that Rust proponents argues that Rust is better for all domains. I don't think anyone says that Rust doesn't have a place, the source of controversy is whether every low level programming task is best done in Rust or not.
https://news.ycombinator.com/item?id=34386997
For 98% of tasks Rust is incredible. But modeling those 2% of problems that don't fit neatly into Rusts domain, the level of "unsafe", "PhantomData", and advanced type-system hacks you need feels obscene when I can use some pointers in C++.
I will openly say I think Rust is an all-around better language. If you are building general-purpose systems software, or just want to write fast software, use Rust.
If you are fiddling with bits, doing low-level concurrency, writing a GC/Memory Manager etc, or have self-referential datastructures like graphs or trees, you're going to have an order of magnitude easier time writing it in C++. (Rust-experts excepted)
I don't agree. This happens only if you want to not make any compromises on the performance and stay on the safe side, which is a goal unreachable for the majority of languages. Often an Arc/Rc/Refcell + a few clones makes the borrow checker shut up and the code is not more complex than it would be in another GCed language. And often still faster (although YMMV), because those few clone calls may totally not matter, and Rust compiler runs circles around compilers/runtimes used in most popular GCed languages like Java, Go, JS or Python.
You can also use raw pointers which is not different from using raw pointers in C++. I have no idea why some people say using pointers in C++ is fine but writing some parts of a program in unsafe block in Rust is a problem.
Ex:
using (var sr = new StreamReader(filename))
{
txt = sr.ReadToEnd();
}
https://learn.microsoft.com/en-us/dotnet/api/system.idisposa... using var sr = new StreamReader(filename);
txt = sr.ReadToEnd();
And for the "what about forgeting to call using?",https://devblogs.microsoft.com/dotnet/infer-interprocedural-...
No different from using all those static analisers in C and C++ to keep the code free of memory corruptions and UB issues.
Though there was nothing inherent to ever block you from doing it (just create an object that implements Closeable, and malloc in the constructor, free in the close method), this new API makes it quite great. The basic API gives you a SegmentScope which denotes a lifetime, and MemorySegments that point to a memory region (pointer+size+layout+scope). MemorySegments have both spatial and temporal safety, so accessing memory out of boundary will only throw an exception instead of corrupting memory, while any access outside the lifetime of the scope is forbidden. Oh, and they are also thread-safe, only optionally being shared between threads.
So in practice it you can write something like
try (var arena = Arena.openConfined()) {
MemorySegment segment = arena.allocate(someLayout);
// use segment
} // memory will be deallocated hereI also have written about patterns i find useful when dealing with existing C codebases at https://ramanlabs.in/static/blog/raw_memory_management_with_...
Wish the big corps had backed it, or Zig, over drab stuff like Go.
Bug reports, sure. But as for security vulnerabilities, both Microsoft and Google have reported that roughly 70% of them were memory safety issues.
Personally, as a Rust fan, I fully agree that the borrow checker limits developer velocity, and I'd be happy to toss it if I could. But verdagon has been promising memory safety with no overhead for years; I'll believe it when I see it.
Edit: Okay, that was unfair to say without further context, so let me add two things. First, although Vale is open source, the design has changed over time and the implementation was described as a "work-in-progress" as of two months ago. [1] Second, a month before that, verdagon described the design as providing "statistical safety", meaning use-after-frees may fail to be caught but are caught "99.9999999999996% of the time". Now, stochastic exploit mitigations such as ASLR and PAC are valid defenses, as long as that 99...% also applies in the presence of an active attacker. But the specific design described (allowing generation counters to wrap) sounds like, in many scenarios, an attacker could manipulate it to provide no protection at all. There may be ways to mitigate this, but in context it does sound like it's designed under the assumption that memory safety violations will occur at random, rather than being designed to defend against attacks. This might be good enough for (some) games, but not for most Rust use cases.
[1] https://verdagon.dev/blog/zero-cost-memory-safety-regions-ov...
No? Those developers can take time off, but the electricity can not. I think he showed how important optimizing software performance is at scale. When you electric bill is $3Billion, you gotta write efficient code, not write more code as fast as possible.
But the vast majority of those 27.000 engineers do not work on such low level routines, but on things like millions of lines of crufty Python that power Adsense analytics, which are essential for maintaining a revenue stream. Yes, development speed is very important but it's mostly orthogonal to other operational costs if the right tools and architectures are employed.
But if I'm being pro-Rust, I would also say that not all coding is affected by a memory safety approach's downsides; there are some domains where the borrow checker doesn't slow development velocity down at all.
Either way, I definitely agree that its orthogonal to many operational costs. I'll mention this line of thought in the article. Thanks!
The author argues it's similar for startups. I agree to a point. For GC in particular I think the costs are underestimated. Processes blowing up because of memory leaks (which can still happen), dealing with stop-the-world ("STW") pauses and inconsistent performance are all real problems.
So much of this is situational. For example, I think Objective-C/Swift was so successful on iOS (vs Java on Android) in part because it opts for reference counting instead of GC. This is predictable performance and less complicated. But RC isn't necessarily appropriate for a server as you may create hot spots and degrade performance that way.
Rust by virtue of borrow checking isn't a panacea. It does however greatly reduce the chances of making a whole class of really important bugs (ie memory safety and all the entails like buffer overruns). Comparing it to Python, Go or Java doesn't necessarily make sense. Rust lives in the same domain as C/C++.
One funny side note:
> Google used 15.5 terawatt hours of electricity in 2020
Bitcoin used 200 TWh in 2022 [1]. Think about that.
[1]: https://www.cnet.com/personal-finance/crypto/bitcoin-mining-...
I love Rust's developer experience, frankly. The macros and build system are the main reason I like the language. I hate the borrow checker most of the time, for exactly the reasons listed in the article. It's probably a terrible choice for a 7DRL project, unless it was some sort of Rust core with content that was primarily orchestrated from another language (possibly DSL).
I would wager introducing a small rust hot loop via FFI into a managed language is a much saner route, using the respective languages to the best of their abilities.
If you do have a bigger object graph, not using it anymore will recursively go on and decrement the counters, removing potentially many objects from memory, totally killing the given thread’s cache/performance. All that is amortized with a tracing GC. On top, atomic counters are very expensive.
Of course you can probably get away with using (A)RC at few places at most with Rust, but if you decide to track object lifetimes with RC for loads of objects, it will have a price (which may or may not be worthy. In my opinion, it is better to stick to Rust for low-level programs)
On the contrary, I hate the macros, because the formater can't format inside tokio select or some of the other macros, and that means I have to do it manually, which is lame.
As a side note, there's a poorly-documented behavior where rustfmt will format macros that are wrapped in {} if it's valid rust code. So you can even point the blame on tokio for not using formatter-friendly macros.
I dislike them primarily because of the additional complexity they add that isn't always justified. All of the more complex macros I wrote I regret, especially the one I did at my last job since now others had to maintain it.
Some macro use can be justified, eg serde. But I try to avoid using proc macros where I can (and certainly never try write one anymore)
Source on AMD CPUs having support for CHERI-style capabilities ? Afaik, there is only the Arm Morello prototype out right now and FPGAs.
Developer velocity in Rust is a complex topic, and yes sometimes Rust will slow you down, but sometimes also it makes you move much faster. You can find a lots of testimonial in all directions, the majority of Rust users having a good opinion on Rust on that topic (but we're biased too). But somehow this article only chose to quote people complaining about developer velocity with the language and completely ignore the other (and larger) group of developers, praising the language user experience and development velocity.
I don't think Rust is perfect, and I think that people talking about how “Rust is the most loved language on Stack overflow for the past N years” isn't really constructive. But a collection of cherry-picked quotes showing how Rust is in fact a disaster isn't bringing anything interesting to the discussion either…
What language have both GC and deterministic destructors, and how does it works? I'd guess it couldn't be tracing GC (because the GC runs whenever it wants, so you don't have determinism, or at least that's my understanding). Or maybe it's something like RAII for non-memory resources (with a destructor that doesn't free memory), and a GC for memory management?
Basically you can use GC heap as usual, or native heap with untraced references, stack or global memory segments.
Value types with constructors/destructors pairs are also supported.
So you have all the building blocks to do C++ style RAII when needed, otherwise relax and enjoy the GC.
Anyway, I think you're referring to `defer`? Though perhaps you're referring to something else, as defer also appears in non-GC'd languages as well so it didn't seem like a very relevant factor in this specific comparison. I do agree that defer (and try-with-resources) can be stellar tools for GC'd languages.
Programs with Rust will always be rock-stable, unlike many C/C++ programs which are more like a house-of-cards.
But this article isn't that, and to make manual memory management more appealing they had to ridiculously inflate the issues that come with ownership-based memory management…
There's not trying to hide that they are biaised, at this point it's Kremlin-level of shameless bad faith.
[1] it looks like they've harvested Rust criticism for an entire year at this point, since they end up even quoting random discord comment from more than a year ago: https://discord.com/channels/273534239310479360/818964227783...
There are actually 45 citations in the article on all angles, but I think you're talking specifically about the anecdotes.
Regarding the anecdotes, I had to add more of those to the borrow checking sections because it was the most surprising to my initial readers. Very little discussion online actually compares borrow checking to higher-level languages with good development velocity; most discussion online compares it to languages like C, C++, Javascript, or Python, so this was new to most readers.
The article also explicitly mentioned that those were anecdotes and colored them differently, so that people didn't mistake them as data.
They also made that part of the article much longer than it was originally.
I can see how that could come across as biased. Perhaps I should have added citations to the other parts of the article so their distribution was more uniform.
When you look at the content itself, it's pretty balanced I'd say (hence the focusing on the other benefits of borrow checking plus the downsides of GC), it's unfortunate that's not coming through as much.
How do you know that? Is there any data backing that up?
I've never seen any research showing that a programming language, no matter how strict (Haskell, Ada, Rust) actually improves the reliability of software, except for comparisons between memory-safe and non-memory-safe languages. It almost always goes down nearly entirely to development process and team skills/experience, showing anything else convincingly would be a huge breakthrough.
Based on the buggy and unstable Python desktop apps I have used, I have a strong suspicion that developing large applications in Python is strongly self-limiting after the initial sprint.
Some of my own Rust code is moderately complex but never showed any signs of instability during development. I often have crashes now and again with my C++ programs. Sure, I fix those afterwards but getting it flawless every time the first time is (for me at least) unheard of.
Most discussions and sources online compare Rust to easy targets, like C, C++, or Python, so we never get to really dive into the more interesting comparisons against stronger languages (especially GC'd ones). There are also some studies out there that try to measure Rust, but they measure the kinds of complexity that wouldn't translate to real-life coding.
I actually like Rust (it's probably my favorite mainstream language today besides Scala), and if you consider all the dimensions together, there are a lot of situations where Rust is the best choice. However, this article is about one specific dimension (developer velocity) and on that, Rust unfortunately has some drawbacks.
Also, the article does mention situations (concurrency) and aspects (encourages top-down, flattened architectures) that give Rust some advantages.
I tried to be un-biased. Perhaps it didn't quite show, also since the conclusions recommended languages other than Rust when focusing on developer velocity.
Collecting pain point can be useful for the language, as it helps the Rust team to get a better vision of what they can improve, but doing such a one-sided list in an article about comparison to other languages and memory handling mechanism is at best dubious.
> Most discussions and sources online compare Rust to easy targets, like C, C++, or Python
My background is mostly Java then JavaScript and you can quote me on the fact that I found Rust to be more productive than both of them in average, especially when the project last more than a few weeks. The biggest underlying reason is different though, JS mostly suffering from dynamic typing + overall questionable language and “standard library” design, while Java is plagued by inheritance and dubious OOP patterns. When using both of these languages after Rust I also felt frustrated by the impossibility to assign strong ownership to objects, making sure they aren't being watched or mutated behind my back. Also, billion dollar mistake.
[1]:by rustaceans that is, it's not an objective measurement of any kind, but we're talking about testimonials here.
I didn't need any anecdotes for the borrow checker's benefits because everybody already knows them. I see how that can seem biased though, and next time I add citations to the surprising parts of an article I'll also add them more uniformly to the rest of the article as well.
Also, the article was about garbage collection itself versus borrow checking itself, not any specific languages that use them.
You make some valid comparisons between Rust and Java and Javascript, but garbage collected languages don't need to be dynamically typed, and don't need to have null, and don't need to have OOP patterns. When you compare Rust to a more modern language like Scala or Pony, you get a much truer comparison of the approaches.
That's what the article is really comparing: borrow checking versus garbage collection. Not the extra features that are correlated with them in mainstream languages.
Cheers!
In addition, Rust is difficult to use for iOS as it stands today but you can for sure make it support a variety of scenarios with delegates aka callbacks/function pointers/etc. Also RAII can be supported with .drop() and more. Half of the cons are incorrect and misrepresent current state of affairs.
It also recommends defaulting to Go over C# for making a web server which my religious views compel me to publicly object to :)
The article was trying to focus on the general approach of GC more than the GC'd languages themselves, and the article was already pretty long so I tried to keep it scoped to just developer velocity implications of the memory safety approaches themselves.
I did mention some of those aspects, for example how C# is more than just a GC'd language (it has value types to help avoid GC), and Rust is more than just borrow checking (it has RefCell). But I couldn't get as deeply into these other aspects as I would like.
(It's also pretty funny that the article's been called biased by both sides, in both directions now! Such is life.)
I also don't mean to specifically recommend Go over C#, and I see the line that gives that impression, fixing now.
My main issue with it is the focus on short term developer velocity. Fine if you are writing a game in a week and then stopping. Most people are not doing that.
It has a great list of interesting new languages to look at!
> Graphs; it generally only thrives with a strict tree hierarchy of data.
This is actually now supported: https://doc.rust-lang.org/std/rc/struct.Rc.html#method.new_c...
That being said, Rust has taught me that I was vastly overusing graphs (structurally, not algorithmically) in my code. Graphs are extremely difficult to reason about; a fact that I learned after approaching an unfamiliar Rust codebase for the first time. It was the brain equivalent of putting down a screwdriver and picking up a powertool, my mind just relaxed.
That alone is one reason I think that every developer should try and "suffer" under that constraint for at least one or two real problems (whether with Rust, or some other language). You'll end up writing better code in your preferred language.
The removal of a feature is sometimes a feature. While roads limit where you can drive, they also mean you don't have to figure out how to traverse a mountain in a Lambo.
But yeah, knowing how to program like that is useful, learning new style will never hurt, but being forced to code like you code in competitive programming isn't a good thing for a language.
I completely disagree with this statement. If a Rust developer takes longer to code some feature this time will eventually be saved fixing memory bugs later on. And don't forget the immaterial cost of losing customer confidence if the product crashes or glitches because of memory instability.
And then I'm even glossing over the enormous benefit of Rust when writing correct multi-threaded code, which is almost always a minefield in C/C++. Code that looks and seems to work OK might in production suddenly crash after a year or so. A complete nightmare!
Memory safety is ALWAYS a good thing to have.
I spent most of my career in front-end, where such considerations are way down the priority list, but even I was able to produce something in Rust that compiles.
That being said I never understood why would anyone want to use this language for web development - most problems are solved in that space, so if you're not out to tackle tje unsolved ones, it might not be worth it.
On the backend, at least, Rust would be a significant improvement over Ruby, for example, once a business has gotten past the initial prototyping phase. I've spent years in Ruby development, and the duck typing makes refactoring a production system scary and drama-prone. No matter how carefully you proceed, there will be gaps in test coverage, and you'll regularly see new bugs in production from the refactoring work that would never happen in a typed language. I would gladly work in Rust over Ruby in a web development context. (Although my preferences lean towards Rust, there are no doubt many other typed languages that could do a good job here as well.)
You don't disagree at all. "Later on" may never come to your start up , you're actually helping make that point for the author.
Nim is one of the first languages to use borrow checking to prevent copies and ref counts, and it's done both explicitly with the experimental 'views' feature and when permitted implicitly.
Nowadays basically everything is exposed to the network.
This is true in a single-threaded context, but in a multithreaded (or interruptible) context, aliasing-xor-mutability is the real rule for everything other than atomics. Breaking that rule is a data race, which is per se UB in C/C++/Rust/Zig/Go/Swift.
It builds a borrow checker on top of any user-specified allocator, whether that be single ownership, reference counting, arenas, garbage collection, or any other user-specified strategy. A user can even code their own.
It's a very promising approach, because it preserves the borrow checker's strengths while addressing its the borrow checker's development velocity downsides. GC and RC often make a program's overall architecture looser and more flexible, but by offering borrow checking for everything else, it allows a program to much more cut down on its memory safety overhead.
What Cone does differently than other languages is that it decouples the allocation strategy from the type of data you're working with. This makes it much easier to change your code to use different memory safety styles. In a way, it's like Rust but allows for more flexibility on memory management.
It's still in progress, but I encourage anyone interested in languages and memory safety to take a look: https://cone.jondgoodwin.com/
…Well, except for certain magic features that the standard library types get that you can’t (yet) replicate in a custom type. This is an annoying wart, but they’re relatively minor.
But yeah, custom allocator support for standard library containers is planned.
I think Rust is missing a fair bit more than "user-specified local allocators" to be on feature parity with C++. Curious if it plans to be on full feature parity.
What authority declares implementation inheritance bad may I ask? As long as one does not stick it into inappropriate places it is actually very helpful.
2. it comes with a non-negligible complexity cost to the language (e.g. what if you do multiple inheritance, what code gets called at object construction etc)
You probably don't want to end up with a language that implements all ideas that are good in some context. Especially you don't want to have different mechanisms for achieving the same thing.
This is no argument from my point of view. Everything can be replaces with simpler things. We are not going back to assembly however except very few cases.
"2. it comes with a non-negligible complexity cost to the language (e.g. what if you do multiple inheritance, what code gets called at object construction etc)"
Every feature has complexity costs and I prefer to have options here. It should be me who decides what I am willing to trade and what for.
>"Especially you don't want to have different mechanisms for achieving the same thing."
They're not exactly the same thing and actually this is exactly what I want. I am very averse of super opinionating "my way or highway" type of things. Oh and btw from a glance Rust seems to have different mechanisms for the same thing too, for example with all those option / unwrapping / question mark.
The question was asked about "by what authority". Your arguments are just an opinion not any better than mine.
Strictly speaking, open recursion (a notorious footgun in implementation inheritance, and arguably the underlying cause of the "fragile base class" problem) cannot be implemented via composition as-is. You need a complex "tie-the-knot" construction, typing "self" as an existential type defined in terms of itself, to enforce an indirection through the object's vtable in all calls to overridable methods. (AIUI, Rust does not have existential types that are general enough to allow this, albeit that might happen at some point as part of overall improvements to the type system.)
- Experimentation on the idea, algorithm, data structure,..., everything that actually produces some result.
- Improve and finalize the specification of input, output, resource constraint, and eventually the high level architecture (modularity).
- Refactoring, with best practices and patterns.
- Benchmark usage and eventually swap out the implementation into safe languages.