// 1018: Error Downcast
// Downcasting Box<dyn Error> to concrete type
use std::error::Error;
use std::fmt;
#[derive(Debug)]
struct DatabaseError(String);
impl fmt::Display for DatabaseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "database error: {}", self.0)
}
}
impl Error for DatabaseError {}
#[derive(Debug)]
struct AuthError(String);
impl fmt::Display for AuthError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "auth error: {}", self.0)
}
}
impl Error for AuthError {}
#[derive(Debug)]
struct NetworkError(String);
impl fmt::Display for NetworkError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "network error: {}", self.0)
}
}
impl Error for NetworkError {}
// Functions returning type-erased errors
fn might_fail_db() -> Result<(), Box<dyn Error>> {
Err(Box::new(DatabaseError("timeout".into())))
}
fn might_fail_auth() -> Result<(), Box<dyn Error>> {
Err(Box::new(AuthError("expired token".into())))
}
// Approach 1: downcast_ref โ borrow the concrete type
fn classify_error(err: &(dyn Error + 'static)) -> &'static str {
if err.downcast_ref::<DatabaseError>().is_some() {
"database"
} else if err.downcast_ref::<AuthError>().is_some() {
"auth"
} else if err.downcast_ref::<NetworkError>().is_some() {
"network"
} else {
"unknown"
}
}
// Approach 2: downcast โ take ownership of concrete type
fn handle_error(err: Box<dyn Error>) -> String {
if let Ok(db_err) = err.downcast::<DatabaseError>() {
format!("Handling DB: {}", db_err.0)
} else {
"unhandled error".into()
}
}
// Approach 3: Type ID check
fn is_database_error(err: &(dyn Error + 'static)) -> bool {
err.downcast_ref::<DatabaseError>().is_some()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_downcast_ref_db() {
let err: Box<dyn Error> = Box::new(DatabaseError("test".into()));
assert_eq!(classify_error(err.as_ref()), "database");
let concrete = err.downcast_ref::<DatabaseError>().unwrap();
assert_eq!(concrete.0, "test");
}
#[test]
fn test_downcast_ref_auth() {
let err: Box<dyn Error> = Box::new(AuthError("bad".into()));
assert_eq!(classify_error(err.as_ref()), "auth");
}
#[test]
fn test_downcast_ref_unknown() {
let err: Box<dyn Error> = Box::new(std::io::Error::new(
std::io::ErrorKind::Other, "misc"
));
assert_eq!(classify_error(err.as_ref()), "unknown");
}
#[test]
fn test_downcast_owned() {
let err: Box<dyn Error> = Box::new(DatabaseError("owned".into()));
let result = handle_error(err);
assert_eq!(result, "Handling DB: owned");
}
#[test]
fn test_downcast_owned_wrong_type() {
let err: Box<dyn Error> = Box::new(AuthError("nope".into()));
let result = handle_error(err);
assert_eq!(result, "unhandled error");
}
#[test]
fn test_is_check() {
let err: Box<dyn Error> = Box::new(DatabaseError("x".into()));
assert!(is_database_error(err.as_ref()));
let err: Box<dyn Error> = Box::new(AuthError("x".into()));
assert!(!is_database_error(err.as_ref()));
}
#[test]
fn test_from_result() {
let result = might_fail_db();
let err = result.unwrap_err();
assert!(err.downcast_ref::<DatabaseError>().is_some());
}
}
(* 1018: Error Downcast *)
(* OCaml: pattern matching on exception types *)
(* In OCaml, exceptions are already pattern-matchable *)
exception DatabaseError of string
exception AuthError of string
exception NetworkError of string
(* Approach 1: Direct pattern matching on exceptions *)
let handle_error f =
try Ok (f ())
with
| DatabaseError msg -> Error (Printf.sprintf "DB: %s" msg)
| AuthError msg -> Error (Printf.sprintf "Auth: %s" msg)
| NetworkError msg -> Error (Printf.sprintf "Net: %s" msg)
| exn -> Error (Printf.sprintf "Unknown: %s" (Printexc.to_string exn))
(* Approach 2: Using a general exception container *)
type any_error = .. (* extensible variant type *)
type any_error += DB of string | Auth of string | Net of string
let classify_error (e : any_error) =
match e with
| DB msg -> Printf.sprintf "database: %s" msg
| Auth msg -> Printf.sprintf "auth: %s" msg
| Net msg -> Printf.sprintf "network: %s" msg
| _ -> "unknown error"
(* Approach 3: GADT-style typed errors *)
type _ error_kind =
| DbKind : string error_kind
| AuthKind : string error_kind
type packed_error = Pack : 'a error_kind * 'a -> packed_error
let describe_packed (Pack (kind, value)) =
match kind with
| DbKind -> Printf.sprintf "DB error: %s" value
| AuthKind -> Printf.sprintf "Auth error: %s" value
let test_exceptions () =
let r = handle_error (fun () -> raise (DatabaseError "timeout")) in
assert (r = Error "DB: timeout");
let r = handle_error (fun () -> 42) in
assert (r = Ok 42);
Printf.printf " Approach 1 (exception matching): passed\n"
let test_extensible () =
assert (classify_error (DB "conn failed") = "database: conn failed");
assert (classify_error (Auth "bad token") = "auth: bad token");
Printf.printf " Approach 2 (extensible variants): passed\n"
let test_gadt () =
let e = Pack (DbKind, "query failed") in
assert (describe_packed e = "DB error: query failed");
let e = Pack (AuthKind, "expired") in
assert (describe_packed e = "Auth error: expired");
Printf.printf " Approach 3 (GADT packed): passed\n"
let () =
Printf.printf "Testing error downcast:\n";
test_exceptions ();
test_extensible ();
test_gadt ();
Printf.printf "โ All tests passed\n"