tech

[English] [Japanese]

10.6. Error handling

This section describes LuneScript error handling.

error handling

Currently LuneScript does not support global exits.

If any error occurs, the error should be notified in the return value of the function.

There are two main methods of error notification:

  • Use nilable and represent errors with nil.
  • Use the alge type __Ret.

When to use nilable

By using nilable as the return value of a function, you can express the error of that function.

// @lnsFront: ok
fn func(flag:bool): str! {
   if not flag {
       return nil;
   }
   return "hoge";
}

The above functions return str on success and nil on failure.

This method has the advantage of being simple to implement, but has the disadvantage of not being able to show information about what kind of error has occurred.

To cover this shortcoming, you can cover this shortcoming by returning a multi-value indicating the error content as follows.

// @lnsFront: ok
fn func(flag:bool): str!, str! {
   if not flag {
       return nil, "error";
   }
   return "hoge", nil;
}

When using alge type __Ret

You can use the following alge type __Ret to indicate the contents of the error without using multi-value return.

// @lnsFront: skip
alge __Ret<T1,T2> {
  Ok(val:T1),
  Err(val:T2),
}

The alge type __Ret is available since v1.5.2.

For example, the above func() can be expressed using __Ret as follows:

// @lnsFront: ok
fn func(flag:bool): __Ret<str,str> {
   if not flag {
       return .Err("error");
   }
   return .Ok( "hoge" );
}

! operator

In a function that returns an error, if a sub-function that is executed within the processing of that function returns an error, it is necessary to determine the error for each sub-function, which complicates the processing.

For example, next we have a sub-function sub() and we have func() which calls this function three times.

func() requires error determination processing three times.

// @lnsFront: ok
class Hoge {
   fn sub( val:int ) : int! {
      if val > 3 {
	 return nil;
      }
      return val;
   }
   pub fn func( val:int ) : str! {
      let! work1 = self.sub( val + 1 ) {  // check error
         return nil;
      };
      let! work2 = self.sub( val + 2 ) { // check error
         return nil;
      };
      let! work3 = self.sub( val + 3 ) { // check error
         return nil;
      };
      return "%s" (work1 + work2 + work3);
   }
}
let hoge = new Hoge();
print( "%s" (hoge.func( 0 )) );
print( "%s" (hoge.func( 1 )) );

The ! operator simplifies the above error checking.

Before explaining the specifications of the ! operator, it is easier to imagine what happens when the above code is rewritten using the ! operator, so the code is shown below.

// @lnsFront: ok
class Hoge {
   fn sub( val:int ) : int! {
      if val > 3 {
	 return nil;
      }
      return val;
   }
   pub fn func( val:int ) : str! {
      let work1 = self.sub( val + 1 )!;
      let work2 = self.sub( val + 2 )!;
      let work3 = self.sub( val + 3 )!;
      return "%s" (work1 + work2 + work3);
   }
}
let hoge = new Hoge();
print( "%s" (hoge.func( 0 )) );
print( "%s" (hoge.func( 1 )) );

You can see that the code using ! has simplified error determination processing.

If you want to return two values like this:

// @lnsFront: ok
class Hoge {
   fn sub( val:int ) : int!, str! {
      if val > 3 {
	 return nil, "err";
      }
      return val, nil;
   }
   pub fn func( val:int ) : int!, str! {
      let work1 = self.sub( val + 1 )!;
      let work2 = self.sub( val + 2 )!;
      let work3 = self.sub( val + 3 )!;
      return work1 + work2 + work3, nil;
   }
}
let hoge = new Hoge();
print( "%s,%s" ( hoge.func( 0 )) );
print( "%s,%s" ( hoge.func( 1 )) );

If you use __Ret instead of nilable it will be:

// @lnsFront: ok
class Hoge {
   fn sub( val:int ) : __Ret<int,__Er> {
      if val > 3 {
	 return .Err( __serr( "err" ) );
      }
      return .Ok( val );
   }
   pub fn func( val:int ) : __Ret<str,__Er> {
      let work1 = self.sub( val + 1 )!;
      let work2 = self.sub( val + 2 )!;
      let work3 = self.sub( val + 3 )!;
      return .Ok( "%s" (work1 + work2 + work3) );
   }
}
let hoge = new Hoge();
for count = 0, 1 {
   match hoge.func( count ) {
      case .Ok( val ) {
         print( "ok", val );
      }
      case .Err( err ) {
         print( "ng", err.$txt );
      }
   }
}

Specification of ! operator

To use the ! operator, the following specifications must be met.

  • ! The type T of the value just before the operator is nilable or __Ret type.

    • ! If T is nilable, there may be a type T2 following T.
  • The return type R of a function containing a statement using the ! operator satisfies the following:

    • If T is nilable, then R is also nilable.

      • If there is T2, then there is R2, and R2 is assignable to T2.
    • If T is __Ret<T1,T2> then R is __Ret<R1,R2>.

      • Here R2 must be a type to which T2 can be assigned.
      • T1 and R1 can be of any type.

In the above example using pub fn func( val:int ) : int!, str!,

  • The return value of func() is int!, str!.
  • The type of the value sub() immediately before the ! operator is int!, str!.
  • That is, T1 = int!, T2 = str!, R1 = int!, R2 = str!.
  • Therefore, it becomes T2=R2=str! and the ! operator can be used.

Also, in the sample using __Ret above,

  • The return value of func() is __Ret<str,__Er>.
  • The type of the value sub() immediately before the ! operator is __Ret<int,__Er>.
  • That is, T1 = int, T2 = __Er, R1 = str, R2 = __Er.
  • Therefore, it becomes T2=R2=__Er and the ! operator can be used.

If the above specifications are met, the ! operator behaves as follows.

  • If the value just before the ! operator is .Err, set that value to the return value of the function containing that code and return.
  • If the value immediately preceding the ! operator is .Ok, continue processing with the result of evaluating the ! operator as the value .Ok.

Also, the place where the ! operator can be used is limited to the following statement shown in ../ebnf.

  • stmt_exp
  • if
  • decl_var

For statements other than the above, we will respond at any time.

__Er type

The __Er type is added as a type that indicates information at the time of __Ret error.

This type is the following interface:

// @lnsFront: skip
pub interface __Er {
   fn get_txt():str;
}

In addition, the following __serr() function is provided as a function that creates an instance of __Er type.

// @lnsFront: skip
pub fn __err(mess:str): __Er;

Error handling of multi-value return function using tuple

By using tuples, error handling of multi-value return functions is possible.

// @lnsFront: ok
fn sub(flag:bool) : (int,str)!,str! {
   if flag {
      return (= 1,"abc"), nil;
   }
   return nil, "err";
}

fn func(flag:bool) : (int,str)!,str! {
   let val1, val2 = sub(flag)!...;
   let val3, val4 = sub(flag)!...;
   print( val1, val2, val3, val4 );
   return (= val1 + val3, val2 .. val4 ), nil;
}

foreach cond in [true,false] {
   let tuple, err = func( cond );
   when! tuple {
      let val1,val2 = tuple...;
      print( "ok", val1, val2 );
   } else {
      print( "ng", err );
   }
}

When using __Ret

__Ret is an alge type representing values between Ok and Err. It is a value of one type that can hold both Ok and Err.

LuneScript can use multi-value return, but only one __Ret can be returned when used for error handling purposes.

Therefore, by using a tuple for the type held by __Ret, error handling can be performed in the same way as multi-value return.

Below is an example of processing a combination of __Ret and a tuple.

Running sub() inside func(). At this time, ! is used to transfer errors, and a tuple is used to achieve the same processing as multi-value return.

// @lnsFront: ok
fn sub(flag:bool) : __Ret<(int,str),__Er> {
   if flag {
      return .Ok( (= 1,"abc") );
   }
   return .Err( __serr( "err" ));
}

fn func(flag:bool) : __Ret<int,__Er> {
   let val1, val2 = sub(flag)!...;
   let val3, val4 = sub(flag)!...;
   print( val1, val2, val3, val4 );
   return .Ok( val1 + val3 );
}

foreach cond in [true,false] {
   match func( cond ) {
      case .Ok( val ) {
         print( "ok", val );
      }
      case .Err( val ) {
         print( "ng", val.$txt );
      }
   }
}