Understanding inferred type in eio


I’ve created a simple echo server using eio:

open Eio

let handler socket _addr =
  traceln "Server accepted connection from client";
  Eio.Flow.copy socket socket;
  traceln "Connection closed"

let on_error e = traceln "Error handling connection: %a" Fmt.exn e

let run_server socket =
  Switch.run @@ fun sw ->
  let rec loop () =
    Eio.Net.accept_fork ~sw ~on_error socket handler;
    loop ()
  loop ()

let main ~net ~addr =
  Switch.run @@ fun sw ->
  let socket = Eio.Net.listen net ~sw ~reuse_addr:true ~backlog:10 addr in
  traceln "Server ready... Listening on %a" Net.Sockaddr.pp addr;
  run_server socket

let () =
  let addr = `Tcp (Net.Ipaddr.V4.any, 8080) in
  Eio_main.run @@ fun env -> main ~net:(Eio.Stdenv.net env) ~addr

What I’m interested to understand is the inferred type of socket in the handler function.

According to ocaml-lsp it is:

< copy : 'b. (#Flow.source as 'b) -> unit
; probe : 'a. 'a Generic.ty -> 'a option
; read_into : Cstruct.t -> int
; read_methods : Flow.read_method list
; write : Cstruct.t list -> unit
; .. >

I believe this is something to do with the object system but I haven’t used that before and would love some help unpacking what it means.

Also, why do I so often see Cstruct.t in eio, what is that used for?


Eio.Flow.copy has type #Flow.source -> #Flow.sink -> unit.

Since you’re using socket for both arguments, OCaml infers that socket must be both a source and a sink in this case. Since it doesn’t know a specific name for that type, it just lists the operations such a socket must support.

You could help it out by annotating the type, e.g.

let handler (socket : #Eio.Flow.two_way) _addr =

This is a slightly stronger requirement (a two_way also supports shutdown), but then it would simply report the type using that name.

At least in Vim, asking Merlin for the type twice in a row gives the expanded version in all cases.

Cstruct is discussed at Decide on cstruct vs bytes · Issue #140 · ocaml-multicore/eio · GitHub.