-
Notifications
You must be signed in to change notification settings - Fork 200
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
Add a "try-with-resources" or "using" style syntax for automatic resource management #2051
Comments
You could easily accomplish this with closures in Dart as-is: abstract class Resource {
void dispose();
}
void using<T extends Resource>(T resource, void Function(T) fn) {
fn(resource);
resource.dispose();
} Example use: main() {
using(new FileResource(), (file) {
// Use 'file'
});
} It's unlikely this will be added in the language, since unlike Java there is a good way to create your own syntaxes/DSLs with closures today. For some examples, you could look at // Create a Pool that will only allocate 10 resources at once. After 30 seconds
// of inactivity with all resources checked out, the pool will throw an error.
final pool = new Pool(10, timeout: new Duration(seconds: 30));
Future<String> readFile(String path) {
// Since the call to [File.readAsString] is within [withResource], no more
// than ten files will be open at once.
return pool.withResource(() => new File(path).readAsString());
} |
For what it's worth, your example is doable in Java/C# if the |
I can see that Matan's approach will do almost exactly what was requested in this issue, but I still have this nagging suspicion that there could be more to resource finalization than this, and this issue could serve to clarify the situation. With that in mind, I'll reopen this issue and mark it with new labels. Of course, there is no guarantee that we will accept such a language enhancement proposal in the end. |
I am new to Dart, but I am experienced in C# and in Java, and as far as I can see @matanlurey example will not be the same as in Java's It all boils down to Java also takes care to not suppress any I think a feature like this is very welcome to the language, since it makes coding simpler and less error prone to forget to close/dispose a resource, specially on edge cases where EDIT: I found this |
To put code in @matanlurey's mouth void using<T extends Resource>(T resource, void Function(T) fn) {
try {
fn(resource);
} finally {
resource.dispose();
}
} The issue with this approach: you pay for a closure which could be avoided it this was a language feature. |
Yes, and I am not experienced enough in Dart to state safely what's going to happen if I believe that even if you make a |
If this is the case, then having a |
If we were to add this to the language, I think we should do something a little more flexible than C# and Java and have it scoped to the lifetime of a variable. When you have multiple resources that need to be guarded, you get a pyramid of braces, which gets unwieldy: using (var a = new DisposableA()) {
using (var b = new DisposableB()) {
using (var c = new DisposableC()) {
...
}
}
} C# lets you omit the braces, which helps when you have sequential resources: using (var a = new DisposableA())
using (var b = new DisposableB())
using (var c = new DisposableC()) {
...
} But it falls apart if you need to do anything between those using statements: using (var a = new DisposableA())
if (a.Something()) return;
using (var b = new DisposableB())
if (b.AnotherThing()) return;
using (var c = new DisposableC()) {
...
} This either doesn't compile or, if it does, doesn't do what you want. If we're going to do something analogous in Dart, I think we should jump right ahead to what C# is adding with using var a = new DisposableA();
if (a.Something()) return;
using var b = new DisposableB();
if (b.AnotherThing()) return;
using var c = new DisposableC();
... With that, the resource is disposed when the variable goes out of scope. I think it's a much cleaner, more expressive syntax and follows C++'s RAII thing. I don't have a proposal for what syntax we should use for Dart , but something roughly analogous should work. |
It's been awhile since I've written any Java, but IIRC you can put multiple Closeable resources in the same try block and they will be closed in reverse order. try (
CloseableA a = new CloseableA();
CloseableB b = new CloseableB();
CloseableC c = new CloseableC()
) {
...
} |
Having a standard Disposable interface dart-lang/sdk#43490 would enable this to be standard. |
The only reason this needs to be a language feature is (of course 😉) (async) Exceptions. The C++ standard guarantees that even if a destructor throws an Exception, following destructors will still run, and std::terminate is called when a destructor throws during stack unwinding, so not to leave the program in an undefined/wrong state. It's not entirely impossible to implement such behavior in normal Dart code today, I think, though I wouldn't know how to handle Records properly (which weren't even a thing back when you wrote your reply), and it is easy to get it wrong. There are just so many corner cases. So I think this should be handled by the Dart team, and I am really perplexed they haven't dealt with this yet. Don't get me wrong, I love all the new Dart features, but sometimes the Dart team appears a bit unfocused, implementing all sorts of cool stuff while avoiding the really important things like resource handling or intersection types. |
Full C++ RAII is a very different beast from "try-with-resource" or "using". I don't think we're going to aim for C++ RAII. A "using" feature would know precisely which objects are to be disposed, because they are the values of specific expressions which must implement some kinds of using {
var db = await connectToDatabase();
var tr = await db.startTransaction(); // Implements `AsyncCancelable`, its `Future<void> cancel()` rolls back the transaction.
} try {
var response = await tr.query("SELECT * FROM bananas");
} which would invoke This is something you can do using code today, you just need to do one resource at a time, which can be annoying. void using<T extends Cancelable>(T resource, void Function(T) body) {
try {
body(resource);
} finally {
resource.cancel();
}
}
Future<void> usingAsync<T extends Cancelable>(T resource, FutureOr<void> Function(T) body) async {
try {
await body(resource);
} finally {
await resource.cancel();
}
} and then: await usingAsync(connectToDataBase(), (DB db) => usingAsync(db.startTransaction(), (Transaction tr) {
// ...
})); |
Sorry, I didn't want to suggest you guys should go full C++. In fact, I wasn't suggesting any concrete solution! I was just trying to demonstrate why Exceptions are so important to deal with when it comes to resource management, and why I think the C++ standard got it right.
Okay, but how would a bunch of Disposables interact with each other when they all live in their own scope? I mean, that is the exact problem we are trying to solve: multiple Disposables live in the same scope and either construction as well as destruction could fail with an Exception, leaving the other Disposables bad. That's the sole reason I spoke about C++.
But members of it could. And when a function constructs multiple Disposables (and maybe some additional non-Disposables for that matter) and returns them as record, or even as record of records, we still need to deal with failed construction as well as failed destruction. If I had to present a solution, I would absolutely go for D style Scope Guards. It's the most general, expressive, elegant, and flexible solution I could think of. And that is definitely not something that could be implemented in pure Dart today. |
Scope nesting works for that. If cancelling fails, then it throws, but nesting finally blocks will still run. |
One neat feature in Java and C# that I think would be great for Dart is the ability to have a resource be automatically closeable/disposable. This is accomplished in Java with the Closeable and/or AutoCloseable interface paired with the following try-catch syntax:
and in C# with the IDisposable and using syntax:
This helps catch resource leaks because the compiler can detect and warn the developer when close/dispose has not been called or has been called unsafely. I'd love to see this feature in Dart!
Edits: Spelling and grammar fixes
The text was updated successfully, but these errors were encountered: