How to convert your code to be void-safe?
More and more code is becoming void-safe and today you want to turn an existing library into a completely void-safe one. If you start by setting the void-safety level to its highest setting, you might get discouraged quickly by the sheer number of errors. Instead we recommend that you migrate your code step by step by enabling the various void-safety levels, starting from the lowest one until you fix all the errors and going to the next one and repeat the operation until you reach the highest level. At this point you will have a completely void-safe library.
The article does not aim at being exhaustive in all the things you can do to convert code to void-safety, but we believe that if followed it can help a lot in migrating code without introducing ugly code.
- Edit your project configuration file to use void-safe versions of existing libraries you are referring to.
- Make sure that the setting `attached by default' is set to True. It is most likely that the Eiffel standard specification will not allow to change this.
- Read the documentation on how to create a void-safe project from the Eiffel documentation site.
Enabling this level means that now attachment marks are not ignored. If it was just that, your code would compile out of the box. Fortunately (or unfortunately depending on the point of view you are taking), your code is most likely using void-safe libraries such as EiffelBase, EiffelVision, .... This actually might cause your code to fail. For example, the type of the source of an assignment is detachable and the target is attached. This is where you need to ask yourself the question or whether or not you should add the detachable keyword to your type declaration, if not then you need to change the code flow of your code to handle the possible Void value your might get.
In this mode of compilation, the second thing you will find yourself doing the most in addition of adding detachable to type declaration is adding object tests. For example, if you had:
you will need to do:
Once you have fixed all those errors, you will gain the following:
- A better understanding of your code by clearly marking what is attached and what is detachable
- A step closer to complete void-safety
At the end of this steps, perform your regression testing as everything should still work as before.
Once your code compiles at the conformance level, enabling the initialization level will most likely cause errors for all attached entities that are not set before usage. To be precise:
- For attached attributes, they need to be properly initialized in all creation procedures of the current class and its descendants.
- For attached locals, they need to be set via an explicit creation or via an assignment from an attached expression.
In this step, you might add more detachable declarations. After that it is just a matter of making sure that everything is set before usage. This step should be the quickest of all the steps to convert existing code to void-safety.
At the end of this step, perform all the regression testing.
Now we can go all the way to complete. In this compilation mode, the compiler will now check that:
- No direct or indirect usage of Current is done before all attributes have been set.
- All qualified calls are done on attached expressions.
One typical issue you might face in creation procedures is related to Current and the usage of agent on unqualified calls (case of indirect use of Current). You can only use it when all attributes have been set. Which means that calls involving Current or agent creation on unqualified calls need to be postponed to the very end of a creation procedure.
Depending on the library it might be easy, or it could be difficult and require you to change some fundamental designs. Here there is not much recommendation we can make, it is up to you to step back and think more about the whole architecture.
Once you have achieved this goal, perform your final regression testing to be sure nothing is broken. Congratulation! You just made your library completely void-safe.
The thing not to do
Never use anything like to please the compiler in thinking that the code is void-safe.
If not done properly, it means you will get an exception at runtime which is most likely something you do not want. Instead you have to think about the reason why you think it is indeed correct (see below). In very few cases, you have to resort to this but it should be done very carefully with comments to explain the rationale why you cannot do otherwise.
There is a precondition that states it is attached. In this case, I distinguish 4 cases depending on the nature of the routine:
- Boolean query: Depending of the nature of the query, often answering False is always good, so you can remove the precondition and simply have:
- Detachable query: the result is detachable, this is very similar to the boolean query case, and you can thus remove the precondition and simply have:
- Attached query: this is where it is probably the most difficult thing to answer. If you can create a dummy object then
this is completely fine because if you violate the precondition, you still get a precondition violation. The check instruction is to show the reader you understood that this should not happen, and the creation to satisfy void-safety. In the event you are running with precondition disabled and check enabled, you will catch this defect too. If you run without any assertions, you are getting an unspecified behavior which is the expected outcome when you violate a precondition.
- Command: it is like an attached query in much simpler since we just need to keep the check:
The state of the current instance tells you it is attached. But if there is nothing to tell you otherwise, it means you are lacking some contracts, either invariants or preconditions. The resolution is often close to what is described for when the attached status of an entity is given by the precondition.
Things to look for
Thanks for Alexander Kogtenkov to raising this. They are a few things you should look out for that can help finding out when to use an attached or detachable type.
Type of arguments
If you have an existing precondition checking that the argument cannot be Void, you should make sure that the type of the corresponding formal argument is attached. When it is a class type, usually there is nothing to do since it is attached by default. But if this is an anchor to something detachable, you need to add the attached mark.
On the other hand, if by reading the precondition you see that the argument may be Void, then you should change the type declaration to be detachable.
Type of Result
Similarly to arguments, if the postcondition states
Sometime you have some attributes of a class that do not need to be set in the creation procedure and that once set will always be attached. We call them stable attributes. Although they are not standardized, we have introduced a syntax for them:
This will tell the compiler that it is ok to use this attribute once it has been proven to be set, and the compiler will forbid reattachment where the source is detachable.
If it was not stable, you would have to use an object test to use it.