Handling Errors

Objectives: Write an application where errors can be signalled in multiple places. Write common error handler that prints error message if error occures. Otherwise display execution results. Error handler should be able to distinguish errors.

Python

from contextlib import contextmanager

def div(a: float, b: float) -> float:
  if b == 0.0:
      raise ZeroDivisionError("Division by zero is illegal")
  return a / b

def div_and_add(a: float, b: float) -> float:
  n = div(a, b)
  m = div(b, a)
  s = m + n
  if s >= 10:
      raise OverflowError("Sum is too large")
  return s

def show(a, b):
    try:
        res = div_and_add(a, b)
    except ZeroDivisionError as exc:
        print(f"Zero division error: {exc}")
    except OverflowError as exc:
        print(f"Overflow error: {exc}")
    except Exception as exc:
        print(f"Error: {exc}")
    else:
        print(f"Result: {res}")

show(2.0, 4.0)   # Result: 2.5
show(0.0, 4.0)   # Zero division error: Division by zero is illegal
show(2.0, 0.0)   # Zero division error: Division by zero is illegal
show(100.0, 1.0) # Overflow error: Sum is too large

Rust

fn div(a: f32, b: f32) -> Result<f32, &'static str> {
    if b == 0.0 {
        return Err("Division by zero is illegal");
    }
    Ok(a / b)
}

fn div_and_add(a: f32, b: f32) -> Result<f32, &'static str> {
    let n = div(a, b)?;
    let m = div(b, a)?;
    let s = m + n;

    if s >= 10.0 {
        return Err("Sum is too large")
    }

    Ok(s)
}

fn show(a: f32, b: f32)  {

    fn disp_res(x : &dyn std::fmt::Display ) {
        println!("Result: {}", x);
    }

    fn disp_err(x : &dyn std::fmt::Display ) {
        println!("Error: {}", x);
    }
    
    match div_and_add(a, b) {
        Ok(res) => { disp_res(&res); },
        Err(msg) => { disp_err(&msg); }
    };
}

fn main() {
    show(2.0, 4.0);   // Result: 2.5
    show(0.0, 4.0);   // Error: Division by zero is illegal
    show(2.0, 0.0);   // Error: Division by zero is illegal
    show(100.0, 1.0); // Error: Sum is too large
}

Note: in the example above, in the error handler there is no way to distinguish error types (other way than looking at the error message).

Alternatively:

#[derive(Debug)]
enum DivError {
  DivisionByZero(String),
}

#[derive(Debug)]
enum AddError {
  Overflow(String),
}

type DivResult = Result<f32, Box<dyn std::any::Any>>;

fn div(a: f32, b: f32) -> DivResult {
  if b == 0.0 {
    return Err(Box::new(DivError::DivisionByZero(
      "Division by zero is illegal".to_string())));
  }
  Ok(a / b)
}

fn div_and_add(a: f32, b: f32) -> DivResult {
  let n = div(a, b)?;
  let m = div(b, a)?;
  let s = m + n;

  if s >= 10.0 {
    return Err(Box::new(AddError::Overflow("Sum is too large".to_string())));
  }

  Ok(s)
}

fn show(a: f32, b: f32)  {
    match div_and_add(a, b) {
        Ok(res) => { println!("Result: {}", res); },
        Err(e) => {
            if let Some(v) = e.downcast_ref::<DivError>() {
                match v {
                    DivError::DivisionByZero(msg) => {
                        println!("Zero division error: {}", msg);
                    }
                }
            } else if let Some(v) = e.downcast_ref::<AddError>() {
                match v {
                    AddError::Overflow(msg) => {
                        println!("Overflow error: {}", msg);
                    }
                }
            } else {
                println!("Undetermined error"); // this shouldn't happen
            }
        }
    }
}

fn main() {
    show(2.0, 4.0);   // Result: 2.5
    show(0.0, 4.0);   // Zero division error: Division by zero is illegal
    show(2.0, 0.0);   // Zero division error: Division by zero is illegal
    show(100.0, 1.0); // Overflow error: Sum is too large
}

Note that there are crates in the wild which provide more sophisticated way of handling errors. See thiserror and anyhow.

Crystal

class ZeroDivisionError < Exception
end

class Overflow < Exception
end

def div(a : Float, b : Float) : Float
  if b == 0.0
    raise ZeroDivisionError.new("Division by zero is illegal")
  end
  a / b
end

def div_and_add(a : Float, b : Float) : Float
  n = div(a, b)
  m = div(b, a)
  s = m + n
  if s >= 10
    raise Overflow.new("Sum is too large")
  end

  return s
end

def show(a, b)
  begin
    res = div_and_add(a, b)
  rescue exc : ZeroDivisionError
    puts "Zero division error: #{exc}"
  rescue exc : Overflow
    puts "Overflow error: #{exc}"
  rescue exc
    puts "Error: #{exc}"
  else
    puts "Result: #{res}"
  end
end

show 2.0, 4.0   # Result: 2.5
show 0.0, 4.0   # Zero division error: Division by zero is illegal
show 2.0, 0.0   # Zero division error: Division by zero is illegal
show 100.0, 1.0 # Overflow error: Sum is too large