Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Auto Closing #119

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 95 additions & 0 deletions proposals/0000-auto-closing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# Auto Closing

* Proposal: [HXP-NNNN](NNNN-auto-closing.md)
* Author: [Aidan Lee](https://github.com/Aidan63)

## Introduction

Many classes contain a `close` function responsible for cleaning up handles, native resources, or other things which are outside of the control of the GC or which may not want to be kept around until the GC runs. This proposal introduces a new `autoclose` keyword to variable declarations which ensures the `close` function is automatically called when execution of the current block ends.

## Motivation

Leaking resources from these classes is very easy as the user has to manually call `close` and needs to take precautions against exceptions which could prevent the function being executed. Haxe also lacks a `finally` for exceptions which has been the traditional recommendation in other languages which means users have to write excessively verbose try catches to deal with any exceptions and not leak resources.

```haxe
function bar() {
final reader = File.read("input.txt", false);
try
{
final writer = File.write("output.txt", false);

try
{
while (!reader.eof()) {
writer.writeString(reader.readLine());
}
}
catch (exn)
{
writer.close();

throw exn;
}

writer.close();
}
catch (exn) {
reader.close();

throw exn;
}

reader.close();
}
```

You could change the above slightly to a single try catch but you then need to do null checks against each class with a close function to check that it was constructed, so neither option is great.

The `autoclose` keyword solves this problem for the user by ensuring constructed classes with a `close` function have it executed at the end of the code block or if an exception is thrown.

```haxe
autoclose final reader = File.read("input.txt", false);
autoclose final writer = File.write("output.txt", false);

while (!reader.eof()) {
writer.writeLine(reader.readLine());
}
```

## Detailed design

The `autoclose` keyword can be prefixed to any variable delaration expression where the type contains a public `close` function with a `Void->Void` signature. This allows the keyword to be easily used with all existing closable standard library types and 3rd party libraries without modifications requiring some interface to be implemented.

The try catch nesting as shown in the examples in the previous section will be generated for the user to ensure close is called in normal and exceptional cases.

Null checks should be added before `close` is called and in the case of a null variable no `close` call should be made and no exception thrown. Variables of `Dynamic` should not be allow to use the `autoclose` keyword.

Coroutines support can be added by having `autoclose` also be allowed on variable of types which have a `close` coroutine function of type `Void->Unit`. If a class has both a `Void->Void` and coroutine `Void->Unit` function the coroutine one should be used if the variable is declared in a coroutine.

A new `VAutoClose` tvar flag could be added to indicate if a declaration is an autoclose one.

## Impact on existing code

There should be no breaking runtime changes with this proposal, but the introduction of `autoclose` may cause issues with existing code bases with variables of the same name.

## Drawbacks

This auto closing hides the `close` call which can make debugging trickier if you want to step into the function. A large number of `autoclose` variables in a given scope could also lead to many try catches which can be expensive depending on the target.

## Alternatives

The try catch transformation can be done by a build macro and a `:autoclose` metadata quite easily (https://gist.github.com/Aidan63/3ff8baf8cdad659a3bd1750cd751825e), but having it part of the language means it will see greater use and lead to higher quality (less leaky) libraries due to the ease of resource management.

## Opening possibilities

Other languages have extra syntax for scoped resources outside of variable declaration, e.g. Python's `with` or C#'s `using` which can be applied to both variable declarations or used to create a scope for any existing defined variable.

## Unresolved questions

I don't have any strong feelings about the `autoclose` name, so if anyone else has any suggestions for a better / less likely to cause conflicts name then please suggest.

Some standard library times which would benefit from this, such as types in the threading package, don't have a `close` function so wouldn't be usable with this. We may want to allow other functions to be called (`autoclose(function_name)`?) or introduce something like a wrapper abstract with a `close` function.

We may want to allow generators to opt out of the try catch transformation as the target may have a native equivilent. E.g. java 8+ has try resources, C# IDisposable / using, and C++ RAII. These assumably perform better than try catches so it may be useful to allow generators to implement using those.

I went for the structural typing way where anything with a Void->Void close function can be used instead of requiring an interface. Many existing classes in the standard library which would benefit from this are tagged as extern classes so it may not be easy go back and have them implement an interface. We do lose the ability to have stuff like `Array<IClosable>` without resorting to structural typing which is dynamic on many targets. I'm open to reconsidering this if anyone feels strongly.