tryScope<T> static method

Option<T> tryScope<T>(
  1. Option<T> fn(
    1. NonePropagationToken nt
    )
)

Part of the parallel of Rust's try operator that handles the NonePropagation thrown by the method try_. When a NonePropagation is handled, a None is returned.

To ensure the propagation is caught somewhere and to prevent incompatible Err types (thus ensuring compile-time safety), an instance of NonePropagationToken is already provided as the fn argument. To prevent compromising the None propagation strategy, keep the token within the body of the fn callback and other local functions in it (thus, avoid for example copying it to a global variable).

This static method will rethrow any non-NonePropagation error or exception thrown in fn.

Synchronous

Behaves identically to Option.asyncTryScope, but synchronously, thus returning Option<T> rather than Future<Option<T>>.

Try-catch warning

Using try catch in combination with try_ can be done, however thrown Propagations should be handled only by the nidula library, and not — by accident — by your application.

See also: Nidula warning: None/Err propagation with try-catch blocks.

Examples

Example 1:

In this example, Option.tryScope returns an Option<int>. If the passed argument l is Some, example1 returns a Some; else if the passed argument l is None, example1 returns None.

Option<int> example1(Option<int> l) {
  return Option.tryScope<int>((nt) {
    l = Some(l.try_(nt) + [1, 2, 3].elementAt(1)); // it will propagate now if initial `l` was None
    return Some(l.try_(nt)); // it will return a Some if initial `l` was Some
  });
}

Example 2:

In the next example, Option.tryScope always returns a None<int>.

Option<int> example2(Option<int> l) {
  return Option.tryScope<int>((nt) {
    l = Some(l.try_(nt) + [1, 2, 3].elementAt(1)); // it will propagate now if initial `l` was None
    l = None(); // not propagating yet
    l.try_(nt); // it will propagate now if initial `l` was Some
    l = Some(l.try_(nt) + [5, 6].elementAt(1)); // dead code (not detected by IDE)
    return Some(l.try_(nt));
  });
}

Example 3:

The following example, we also want to repackage any RangeError that are thrown in the body of Option.tryScope. While this functionality can be useful in specific cases, it's important to note that it deviates from the primary purpose of the parallel of the try operator.

Option<int> example3(Option<int> l) {
  try {
    return Option.tryScope<int>((nt) {
      l = Some(l.try_(nt) + [1, 2, 3].elementAt(100)); // it will propagate now if initial `l` was None, else it will throw a RangeError
      l = None(); // dead code (not detected by IDE)
      l.try_(nt);
      l = Some(l.try_(nt) + [5, 6].elementAt(1));
      return Some(l.try_(nt));
    });
  } on RangeError {
    return None();
  }
}

Implementation

static Option<T> tryScope<T>(Option<T> Function(NonePropagationToken nt) fn) {
  try {
    return fn(const NonePropagationToken._());
  } on NonePropagation {
    return None();
  }
}