Eio 0.1 - effects-based direct-style IO for OCaml 5

Eio_main.run is like Lwt_main.run. It is typically used once in an application, at the start, and everything runs inside it. You’d use it like this (which I would however consider bad style):

module Globals = struct
  let fs = ref None

  let init env =
    fs := Some (Eio.Stdenv.fs env)
  
  let load path =
    Eio.Dir.load (Option.get !fs) path

  let save path data =
    Eio.Dir.save (Option.get !fs) path data ~create:(`Exclusive 0o666)
end

let main () =
  let data = Globals.load "input.dat" in
  Globals.save "output.dat" data

let () =
  Eio_main.run @@ fun env ->
  Globals.init env;
  main ()

Whether the saved value works if you end the main-loop and then start another one would depend on the backend implementation. It’s certainly not guaranteed to work.

If you can make a reliable version with global variables, then the capabilities aren’t giving you any guarantees and could easily be added as a higher-level layer on top of a library that didn’t use them.

OCaml allows any module in my dependencies to run a static initialiser, which can use Obj.magic, call C functions, etc, so we can’t really guarantee anything about any non-trivial program. It would be nice if the compiler had some kind of safe mode where it would alert you about unsafe code, but it doesn’t yet.

So when Eio (or any OCaml library) talks about ensuring things, it means assuming you follow some reasonable rules, even if they’re not enforced. Roughly, this means that if you call an “unsafe” function, you need to verify things yourself. From a capability point of view, certain extra functions in the stdlib are considered unsafe, such as open_in, which gets access to the file-system from nothing.

Note that Eio_main.run itself is not a capability-safe function, because gets access to the file-system and network from thin air. If you run the compiler in safe mode, it should therefore flag that call as something you need to audit manually. Which shouldn’t be surprising - obviously you need to read your program’s main entry-point to have any idea what it will do.

To get a bound on what it can do you need to follow the env argument and see where it goes. In the example above, it gets stored in Global, so you need to audit everything that can access that. That would likely be a lot of work, which is why using global variables is generally considered bad style.

It’s also a shame to see classes and object types appearing in what aims to be a foundational library.

Why? Eio uses objects internally, while mostly exposing a functional API. Lwt does the same thing in places (e.g. Lwt_process), and all Lwt code uses Lwt_engine in the end. It doesn’t seem to cause any trouble.

4 Likes