โข Option
โข Result
โข The ? operator propagates errors up the call stack concisely
โข Combinators like .map(), .and_then(), .unwrap_or() chain fallible operations
โข The compiler forces you to handle every error case โ no silent failures
โข Option
โข Result
โข The ? operator propagates errors up the call stack concisely
โข Combinators like .map(), .and_then(), .unwrap_or() chain fallible operations
โข The compiler forces you to handle every error case โ no silent failures
// 1021: Error Propagation Depth
// 5-level error propagation with ?
use std::fmt;
#[derive(Debug, PartialEq)]
enum AppError {
ConfigMissing(String),
ParseFailed(String),
ValidationFailed(String),
ServiceUnavailable(String),
Timeout,
}
impl fmt::Display for AppError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AppError::ConfigMissing(s) => write!(f, "config missing: {}", s),
AppError::ParseFailed(s) => write!(f, "parse failed: {}", s),
AppError::ValidationFailed(s) => write!(f, "validation: {}", s),
AppError::ServiceUnavailable(s) => write!(f, "service unavailable: {}", s),
AppError::Timeout => write!(f, "timeout"),
}
}
}
impl std::error::Error for AppError {}
// Level 1: Config layer
fn read_config(key: &str) -> Result<String, AppError> {
if key == "missing" {
Err(AppError::ConfigMissing(key.into()))
} else {
Ok("8080".into())
}
}
// Level 2: Parse layer
fn parse_port(s: &str) -> Result<u16, AppError> {
s.parse::<u16>()
.map_err(|_| AppError::ParseFailed(s.into()))
}
// Level 3: Validation layer
fn validate_port(port: u16) -> Result<u16, AppError> {
if port == 0 {
Err(AppError::ValidationFailed(format!("port {} invalid", port)))
} else {
Ok(port)
}
}
// Level 4: Connection layer
fn connect(_host: &str, port: u16) -> Result<String, AppError> {
if port == 9999 {
Err(AppError::ServiceUnavailable("connection refused".into()))
} else {
Ok(format!("connected:{}", port))
}
}
// Level 5: Application layer โ chains all with ?
fn start_service(key: &str, host: &str) -> Result<String, AppError> {
let raw = read_config(key)?; // Level 1
let port = parse_port(&raw)?; // Level 2
let valid = validate_port(port)?; // Level 3
let conn = connect(host, valid)?; // Level 4
Ok(conn) // Level 5 success
}
fn main() {
let cases = vec![
("port", "localhost"),
("missing", "localhost"),
];
for (key, host) in cases {
match start_service(key, host) {
Ok(conn) => println!("Success: {}", conn),
Err(e) => println!("Error: {}", e),
}
}
println!("Run `cargo test` to verify all examples.");
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_full_success() {
assert_eq!(start_service("port", "localhost"), Ok("connected:8080".into()));
}
#[test]
fn test_level1_config_error() {
let err = start_service("missing", "localhost").unwrap_err();
assert!(matches!(err, AppError::ConfigMissing(_)));
}
#[test]
fn test_level2_parse_error() {
// parse_port directly
let err = parse_port("abc").unwrap_err();
assert!(matches!(err, AppError::ParseFailed(_)));
}
#[test]
fn test_level3_validation_error() {
let err = validate_port(0).unwrap_err();
assert!(matches!(err, AppError::ValidationFailed(_)));
}
#[test]
fn test_level4_connection_error() {
let err = connect("host", 9999).unwrap_err();
assert!(matches!(err, AppError::ServiceUnavailable(_)));
}
#[test]
fn test_error_display() {
let err = AppError::ConfigMissing("db_url".into());
assert_eq!(err.to_string(), "config missing: db_url");
let err = AppError::Timeout;
assert_eq!(err.to_string(), "timeout");
}
#[test]
fn test_question_mark_propagates_correctly() {
// Each ? passes the error through unchanged
fn layer_test() -> Result<(), AppError> {
let _ = read_config("missing")?;
Ok(())
}
assert!(matches!(layer_test(), Err(AppError::ConfigMissing(_))));
}
#[test]
fn test_all_layers_independent() {
assert!(read_config("ok").is_ok());
assert!(parse_port("8080").is_ok());
assert!(validate_port(80).is_ok());
assert!(connect("localhost", 80).is_ok());
}
}
(* 1021: Error Propagation Depth *)
(* 5-level error propagation through layers *)
type app_error =
| ConfigMissing of string
| ParseFailed of string
| ValidationFailed of string
| ServiceUnavailable of string
| Timeout
let string_of_error = function
| ConfigMissing s -> Printf.sprintf "config missing: %s" s
| ParseFailed s -> Printf.sprintf "parse failed: %s" s
| ValidationFailed s -> Printf.sprintf "validation: %s" s
| ServiceUnavailable s -> Printf.sprintf "service unavailable: %s" s
| Timeout -> "timeout"
let ( let* ) = Result.bind
(* Level 1: Config layer *)
let read_config key =
if key = "missing" then Error (ConfigMissing key)
else Ok "8080"
(* Level 2: Parse layer *)
let parse_port s =
match int_of_string_opt s with
| None -> Error (ParseFailed s)
| Some n -> Ok n
(* Level 3: Validation layer *)
let validate_port port =
if port < 1 || port > 65535 then
Error (ValidationFailed (Printf.sprintf "port %d out of range" port))
else Ok port
(* Level 4: Connection layer *)
let connect _host port =
if port = 9999 then Error (ServiceUnavailable "connection refused")
else Ok (Printf.sprintf "connected:%d" port)
(* Level 5: Application layer โ chains all 4 *)
let start_service key host =
let* raw = read_config key in
let* port = parse_port raw in
let* valid_port = validate_port port in
let* conn = connect host valid_port in
Ok conn
let test_success () =
assert (start_service "port" "localhost" = Ok "connected:8080");
Printf.printf " Success path: passed\n"
let test_config_error () =
(match start_service "missing" "localhost" with
| Error (ConfigMissing _) -> ()
| _ -> assert false);
Printf.printf " Config error (level 1): passed\n"
let test_validation_error () =
(* We'd need a way to make parse return out-of-range...
Let's test validate directly *)
assert (validate_port 0 |> Result.is_error);
assert (validate_port 70000 |> Result.is_error);
assert (validate_port 8080 = Ok 8080);
Printf.printf " Validation error (level 3): passed\n"
let test_all_levels () =
(* Each level can fail independently *)
let results = [
start_service "missing" "localhost"; (* level 1 fail *)
start_service "port" "localhost"; (* success *)
] in
assert (List.length (List.filter Result.is_ok results) = 1);
assert (List.length (List.filter Result.is_error results) = 1);
Printf.printf " All levels: passed\n"
let () =
Printf.printf "Testing 5-level error propagation:\n";
test_success ();
test_config_error ();
test_validation_error ();
test_all_levels ();
Printf.printf "โ All tests passed\n"
| Aspect | OCaml `let` | Rust `?` |
|---|---|---|
| Syntax per layer | `let x = f in` | `let x = f?;` |
| Depth scaling | Linear | Linear |
| Error type | Must match or wrap | `From` auto-converts |
| Without sugar | Nested match | Nested match |
| Readability at 5 levels | Good | Good |
| Performance | Zero-cost | Zero-cost |