Lwt how to raise exception correctly?

On lwt’ manual page, it said

However, It can’t get compiled with these code:
[%lwt raise Not_suppported]. Compiler print Error: Uninterpreted extension 'lwt'.

ocaml 4.11.1
lwt 5.4.0

(I’ll look into that table, maybe it’s out of date. In the mean time.)

There are two ways to raise exception within a program that uses Lwt. Or, more precisely, there are two ways to make a promise be rejected with an exception. Rejecting promises is the Lwt equivalent of raising an exception in vanilla OCaml. (In case you need it, you can read more details about promises, rejections, and other such concepts in this introduction/tutorial.)

The first method is to call Lwt.fail : exn -> 'a Lwt.t. As the type suggests this function does a job similar to raise : exn -> 'a but within Lwt. More specifically, Lwt.fail e returns a promise rejected with the exception e. Rejected promises carry there exceptions through binds. Rejected promises are turned into exceptions at the top level Lwt_main.run. Rejected exceptions can be handled with the dedicated functions Lwt.catch : (unit -> 'a Lwt.t) -> (exn -> 'a Lwt.t) -> 'a Lwt.t.

The second method is to call raise. As long as this happens within a callback passed to an Lwt function, the exception is caught and wrapped in a rejected promise. This rejected promise can be used just as described above. This method of using raise directly is recommended because the exception carries metadata from the OCaml runtime (location and stack-trace).

An example: Lwt-aware traversal of a data-structure.

(* This function finds an element in a {!Stdlib.Seq.t}.
   The [_s] suffix indicates sequential traversal, as per
   the convention of [Lwt_list].

   @raise [Not_found] if no element is found. *)
let rec find_in_seq_s f s =
  match s () with
  | Nil -> raise Not_found
  | Cons (x, s) ->
      f x >>= function
      | true -> Lwt.return x
      | false -> find_in_seq f s

There’s actually a subtle potential bug in this code: The behaviour of the function depends on the size of the seq argument. Specifically, if the seq is empty then the function raises an exception, otherwise it may return a rejected promise.

In practice, this is unlikely to be an issue because you will probably use the function within a chain of binds (or other Lwt functions) and the exception is likely to be captured there and turned into a rejected promise.

If you are writing very precise code and need to take care of that eventuality, you can either

  • turn the raise into an Lwt.fail, or
  • shadow the function so that the first iteration is within a try-with/uses fail (as shown below).
(* Shadowing for safety *)
let find_in_seq_s f s =
  match s () with
  | Nil -> Lwt.fail Not_found
  | Cons (x, s) ->
      f x >>= function
      | true -> Lwt.return x
      | false -> find_in_seq f s

Thank you for detailed explaination. I know Lwt.fail well. I’m confused this thing - Why assert false need to rewrite to assert%lwt false, but raise exn doesn’t need? This drives me to find assert%lwt equivalent to raise.

I’ll let anyone more knowledgeable on the topic correct me, but I think that assert is somewhere between a keyword and a function.

In addition, the syntax extension for Lwt puts a lot of effort into generating code that has useful back-traces and such runtime information. With that in mind, it probably has to special case the assert constructor-function-keyword.

That being said, I’m fairly confident that you can just use assert directly within a callback passed to an Lwt function and that it should behave similarly to raise (Assertion_failure …) or Lwt.fail (Assertion_failure …). It might not produce as good an error message: the trace you print from it will probably mention Lwt’s internals more than your own program; but it should behave the same.

I haven’t used the syntax extension and so I’m not the most qualified to answer on this specific point.

1 Like