Edit: I guess another way to put this is, "Can a non-trivial project be developed in modern Ada without using Unchecked_Dealloation?" Because I don't see how I can consider a library safe if it is allowed to call Unchecked_Deallocation.
Edit: I guess another way to put this is, "Can a non-trivial project be developed in modern Ada without using Unchecked_Dealloation?" Because I don't see how I can consider a library safe if it is allowed to call Unchecked_Deallocation.
1- Don't use pointers. Or use not-null pointers. But preferably don't use them. With in/out semantics you don't need passing pointers or references around, and the addition of official data structures in Ada 2005 make most uses of pointers obsolete. 2- use controlled types when you're allocating to free soon. Limited_Controlled_Type are even safer if you're prepared to deal with a limited type :-) 3- recently, use SPARK, with safe-memory semantics. Hard work, but so is pleasing the borrow-checker... edit Of course one of the impetuses (impetii) of manual memory management is having functions return objects of unknown size (at call site). In Ada this isn't a necessity, at least with GNAT's secondary stack.edit-end
I've programmed Ada almost every day of the work week for 13 years now, I can count on one hand the problems related to memory management, and opening the code you knew you were in for a treat. 'Smells like tiny cache syndrome' just remove those 90's blotches from our humongous codebases when we face them. I'm almost willing to make access types forbidden except in pools (so, not null he), with an open door policy 'explain to me so I can update our coding rules with that case if we find it worth the risk'. Forced software design review by linter.
The Ada folks sometimes seem a bit quiet on memory-management questions, a bit like the way Forth folks rarely talk about the real-world performance of Forth interpreters. On the plus side, borrower-checking is coming to SPARK. [1][2]
[0] https://news.ycombinator.com/item?id=24361992
[1] https://www.adacore.com/papers/safe-dynamic-memory-managemen...
https://docs.adacore.com/live/wave/gtkada/html/gtkada_ug/mem...
Septum uses a lot of dynamically allocated memory, but it's all hidden by smart pointers or Unbounded_String (built-in reference-counted COW strings). I use it routinely to search codebases in the tens of millions of lines and it's fast enough right now (single second search times) due to task parallelism I did in Ada that I haven't even bothering to tune it or do any fancy indexing yet.
Author here. I've actually not had to do any heavy direct memory management yet. There's an RAII-based (i.e. Controlled Type) reference-counted pointer implementation in GNAT I've used and the container libraries is mature enough to avoid the issue by just using built-in data structures.
A few other smart pointer implementations exists floating around in various libraries in Alire, I suspect there will be a default one will appear in Alire at some point which parallels Rust's Arc, Rc and Box, or C++'s std::shared_ptr, std::weak_ptr (for breaking cycles) and std::unique_ptr. I have a partial implementation myself, but I've been working on other things.
- Lexically scoped access types have storage pools that are destroyed when the type goes out of scope. This is similar to region-based memory management as in Cyclone[0].
- Limited controlled types are a lot like linear types and prevent unwanted aliasing and ensure deallocation happens correctly, as described in this paper[1].
[0]: https://en.m.wikipedia.org/wiki/Cyclone_(programming_languag...
But what about cases in large projects with many developers where a library/package allocates an object on the heap and passes it back to a caller? Sometimes it is not clear who is responsible for deallocating the object. This is the challenge we ran into building large projects in C, C++, and Ada. These days, I use Java, C#, Rust, or Go, because I know there will be no dangling pointers.
Limited controlled types handle this case.
Limited types prevent assignment (i.e. aliasing). Controlled types introduce destructors so everything is closed even when an exception is thrown.
I think the Ada answer would be to keep everything that has a lifetime, anything that's a resource, in its own module behind a strict limited controlled interface. But that's a very vague answer.
Much like "spaghetti code" is not only considered poor style but isn't even supported by modern languages, "spaghetti data" should also be considered a bad pattern, which more advanced languages force you to avoid.
-- Forward declaration.
Type Element(<>);
-- Assuming there's a Graph.Pool implementation of the base Storage_Pool object.
Type Pointer is access Element
with Storage_Pool => Graph.Pool;
Subtype Handle is not null Pointer;
Type Children is array(Positive range <>) of Handle;
Type Element(Parent : Handle; Child_Count: Natural) is record
Data : Integer; -- Or whatever your actual data would be.
Link : Children( 1..Child_Count );
end record;Unchecked_Deallocation has more in it than just returning storage to the system, it also triggers Finalization:
http://www.ada-auth.org/standards/rm12_w_tc1/html/RM-13-11-2...
"when X is not equal to null first performs finalization of the object designated by X (and any coextensions of the object ...)"
So even if your compiler target has automatic memory management (eg: JVM) you will still use Unchecked_Deallocation if you want to control when Finalization occurs.
To implement the Actor Model in Ada one puts all variables in the body of the tasks that are in the application. It makes them not visible from other tasks. So what you need to keep in mind when developing is for a task to never send an access-to-object type variable to another task. If there is a need to do that you need to use Ada/SPARK or Rust to get the proper ownership checking done. Btw, Codepeer (static code analysis tool for Ada) finds race-conditions, has deadlock detection, and warns if there are variables that may be read or written to by more than one task.
If one sticks to vanilla Ada (not SPARK) one could develop an application based on libadalang that parses all the Ada source code and checks that all task entries have input arguments that do not contain any access-to-object types (to find instances where a developer has sent an access-to-object variable to another task by mistake). Such a tool does not exist but libadalang exists to allow the creation of custom rules checking on one's Ada code.
That is more likely due to the type system than manual memory management. People after all say the same thing about Haskell, which also has powerful types, but is garbage collected.
There are two kinds of code errors to worry about: wrong answers (2+2=5), and divergence (a fancy name for crashing, i.e. 2+2=segmentation fault). In a jet engine controller, wrong answers and segfaults both potentially cause fatalities, so you better not use GC. Ada is made for that.
In (say) a compiler, bugs leading to wrong answers (incorrect code emitted) might cause potential fatalities, but if the compiler segfaults from running out of memory, that's only annoying (the developer must find a workaround, use a bigger computer, or whatever). So it is fine to write a compiler in a GC'd language even if its memory footprint and timing characteristics are hard to verify. If you wrote a compiler in Ada you'd spend a bunch of time with manual memory management, for little benefit.
In fact the most serious formally verified compiler (compcert.inria.fr) is written in Coq, which you can think of as an ultra precise dialect of OCaml and which is GC'd (Coq in this case generates OCaml code that uses the OCaml runtime. It can also generate Haskell etc.).
For example, Ada makes it easy to structure one's code in a strict tree hierarchy (whenever you with a package put pragma Elaborate_All (..) on it, which works well with any Ada compiler I've tried it with and is in the Ada standard since 1995). It makes monolith creation by mistake impossible.
Strange you had memory management problems in an Ada application. With all the focus on safety and security within the Ada community, it makes me wonder how the software engineers were using the language in that project?
Also note that one can run into memory leak problems using automatic garbage collected languages. I've personally needed to track down memory leaks in both C# and Javascript applications. Thankfully this rarely happens. It indicates that even when working in an automatic garbage collected language a developer needs to be aware of potential memory issues and think carefully about architecture.
Glad to hear you were successful in the project (with 100s of developers)!
1. Your project is static enough that it doesn't need dynamic memory allocation. The control system for a modern passenger jet is a pretty complex project, but you can allocate an object per engine, an object per flap, a struct per landing gear, an object per pilot display, etc. at design time. Even the navigational computers on these aircraft, which take in unbounded structured data about airports and navigational points, often have hard-coded limits. Quoting https://www.mitre.org/sites/default/files/pdf/12_1324.pdf :
> one widely used FMC [flight management computer] model ... has issues at airports with over 100 arrival and departure procedures. Some airport examples where 100 arrival and departure procedures are exceeded include Cairo, Amsterdam, Madrid, Paris (Le Bourget, Orly and Charles de Gaulle), Mumbai, New Delhi and Beijing. A FMC service bulletin issued states that the aircraft may lose FMC applications in flight with over 100 procedures and flight plan up-linking. Further, another manufacturer has a FMC model with a limit of 99 total procedures and a limit of 8 waypoints per procedure. A third FMC manufacturer has a model with a limit on the amount of arrivals, departure and approaches as shown below: Early models – limit of 70 departures, 70 arrivals, and 29 approaches at an airport. Later models – limit of 130 departures, 130 arrivals, and 39 approaches at an airport.
Counterintutitively, this is culturally acceptable for such "high assurance" applications, but WhatsApp gets confusion from the press when it increases the maximum number of participants in a group chat to the "oddly specific number" of 256 (https://i.redd.it/gk7dicv6hsvy.jpg).
2. You use a third-party GC/refcounting library that calls Unchecked_Deallocation internally.
3. There's a 2018 proposal (linked in another reply) for "ownership types", which specifically references Rust's lifetime model as an inspiration.
Or, in other words, if your goal is to write a (say) safe implementation of the DOM that can render real-world web pages while efficiently using memory on a general-purpose computer, Ada is probably the wrong tool for the job. Which is fine! It's a great tool for other jobs.
Author here. Ada is actually perfect for the use case you listed. As a C++ programmer focusing on performance, the applicability of Ada to "modern problems" was one of the reasons I was playing around with it. Between customizable "storage pools" (Ada's allocators), the easy of interfacing with C, control of type alignment and layout, compiler intrinsics, built-in concurrency types (protected objects for coordinating access, and tasks for splitting work) and a bunch of other things, I have all the tools I need to do this. It's pretty close to the C++ feature set with the face of Pascal, and C++ programmers should feel comfortable working in Ada after only a few months.
To use storage pools in Ada I would recommend Deepend (https://sourceforge.net/projects/deepend/). Deepend is a storage pool with subpool capabilities for Ada 2012, Ada 2005, and Ada 95. Memory allocations can be associated with subpools and subpools can be deallocated as a whole which provides a safer alternative than managing deletions of individual objects. It also is likely to be more deterministic and efficient than garbage collection.
Yes, there are very few times I've had to manually deallocate; there's a really good video on memory-management with Ada: http://video.fosdem.org/2016/aw1124/memory-management-with-a...