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