Search code examples
pythonocamlwith-statementraii

What is the OCaml counterpart to Python's "with"-statement (automatic release of resources)


What is the OCaml counterpart to Python's "with"-statement?

with open('test.txt', 'r') as f:
    # Do stuff with f
# At this point, f will always be closed, even in case of exceptions

That is: What is the preferred way in OCaml to safely ensure that a certain resource (open file, database connection, HTTP connection, etc.) will always be freed at a certain point in time? Waiting for the garbage collector is no option here, and exceptions should never prevent resources from being freed.

Of course, in OCaml you can always use try-finally and close the file "by hand", as you can do in Python. However, that kind of code is prone to errors. This is why Python introduced the "with"-statement. What's the OCaml idiom to make this kind of code easier to read and less prone to errors?

Note that this question is very different from the question Emulating try-with-finally in OCaml, as this is one step further: I don't just want to emulate try-finally in OCaml! (where Lwt's [%finally ...] does a fine job, by the way.) I want to get one step further, eliminating the need to write those finally-clauses in the first place - as one can do in Python.

Also note that this question is not about implementation details, but about idioms: Which of all possible designs and solutions gained some traction in the OCaml community and is generally accepted?


Solution

  • There is now Fun.protect which may be considered (effectively) the idiom, since it's in the standard library. E.g.,

    let get_contents file =
      let ch = open_in file in
      Fun.protect ~finally:(fun () -> close_in ch) begin fun () ->
        let len = in_channel_length ch in
        let bytes = Bytes.create len in
        ignore (input ch bytes 0 len);
        bytes
      end
    

    Nowadays, there are even let-operators which are slowly finding their way into more frequent use, e.g. https://github.com/ocaml/ocaml/pull/9887

    So you could define a let-op to use a file, like:

    let ( let& ) ch fn =
      Fun.protect ~finally:(fun () -> close_in ch) begin fun () ->
        fn ch
      end
    

    And use it like:

    let get_contents file =
      let& ch = open_in file in
      let len = in_channel_length ch in
      let bytes = Bytes.create len in
      ignore (input ch bytes 0 len);
      bytes
    

    The let& operator makes sure the in_channel is closed at the end of the current scope (get_contents).