Remix.run Logo
cogman10 2 hours ago

Java's is

    try (var foo = new Foo()) {
    }
    // foo.close() is called here.
I like the Java method for things like files because if the there's an exception during the close of a file, the regular `IOException` block handles that error the same as it handles a read or write error.
mort96 2 hours ago | parent [-]

What do you do if you wanna return the file (or an object containing the file) in the happy path but close it in the error path?

cogman10 2 hours ago | parent [-]

You'd write it like this

    void bar() {
      try (var f = foo()) {
        doMoreHappyPath(f);
      }
      catch(IOException ex) {
        handleErrors();
      }
    }

    File foo() throws IOException {
      File f = openFile();
      doHappyPath(f);
      if (badThing) {
        throw new IOException("Bad thing");
      }
      return f;
    }
That said, I think this is a bad practice (IMO). Generally speaking I think the opening and closing of a resource should happen at the same scope.

Making it non-local is a recipe for an accident.

*EDIT* I've made a mistake while writing this, but I'll leave it up there because it demonstrates my point. The file is left open if a bad thing happens.

mort96 2 hours ago | parent [-]

In Java, I agree with you that the opening and closing of a resource should happen at the same scope. This is a reasonable rule in Java, and not following it in Java is a recipe for errors because Java isn't RAII.

In C++ and Rust, that rule doesn't make sense. You can't make the mistake of forgetting to close the file.

That's why I say that Java, Python and C#'s context managers aren't remotely the same. They're useful tools for resource management in their respective languages, just like defer is a useful tool for resource management in Go. They aren't "basically RAII".

cogman10 an hour ago | parent [-]

> You can't make the mistake of forgetting to close the file.

But you can make a few mistakes that can be hard to see. For example, if you put a mutex in an object you can accidentally hold it open for longer than you expect since you've now bound the life of the mutex to the life of the object you attached it to. Or you can hold a connection to a DB or a file open for longer than you expected by merely leaking out the file handle and not promptly closing it when you are finished with it.

Trying to keep resource open and close in the same scope is an ownership thing. Even for C++ or Rust, I'd consider it not great to leak out RAII resources from out of the scope that acquired them. When you spread that sort of ownership throughout the code it becomes hard to conceptualize what the state of a program would be at any given location.

The exception is memory.