โข 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
// 1025: Network Error Classification (Simulated)
// Classifying and handling network-like errors
use std::fmt;
#[derive(Debug)]
enum NetError {
Timeout { seconds: f64 },
ConnectionRefused(String),
DnsResolutionFailed(String),
TlsError(String),
HttpError { status: u16, body: String },
}
impl fmt::Display for NetError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
NetError::Timeout { seconds } => write!(f, "timeout after {:.1}s", seconds),
NetError::ConnectionRefused(host) => write!(f, "connection refused: {}", host),
NetError::DnsResolutionFailed(host) => write!(f, "DNS failed: {}", host),
NetError::TlsError(msg) => write!(f, "TLS error: {}", msg),
NetError::HttpError { status, body } => write!(f, "HTTP {}: {}", status, body),
}
}
}
impl std::error::Error for NetError {}
impl NetError {
fn is_retryable(&self) -> bool {
match self {
NetError::Timeout { .. } => true,
NetError::ConnectionRefused(_) => true,
NetError::DnsResolutionFailed(_) => false,
NetError::TlsError(_) => false,
NetError::HttpError { status, .. } => *status >= 500,
}
}
fn is_client_error(&self) -> bool {
matches!(self, NetError::HttpError { status, .. } if *status >= 400 && *status < 500)
}
}
// Simulated network call
fn fetch(url: &str) -> Result<String, NetError> {
match url {
"" => Err(NetError::DnsResolutionFailed("empty url".into())),
"http://timeout" => Err(NetError::Timeout { seconds: 30.0 }),
"http://refused" => Err(NetError::ConnectionRefused("refused:80".into())),
"http://500" => Err(NetError::HttpError {
status: 500,
body: "Internal Server Error".into(),
}),
"http://404" => Err(NetError::HttpError {
status: 404,
body: "Not Found".into(),
}),
url => Ok(format!("response from {}", url)),
}
}
// Retry logic
fn fetch_with_retry(url: &str, max_retries: u32) -> Result<String, NetError> {
let mut last_error = None;
for attempt in 0..=max_retries {
match fetch(url) {
Ok(response) => return Ok(response),
Err(e) if e.is_retryable() && attempt < max_retries => {
last_error = Some(e);
// In real code: sleep with exponential backoff
continue;
}
Err(e) => return Err(e),
}
}
Err(last_error.unwrap())
}
fn main() {
let urls = &["http://example.com", "http://timeout", "http://404", ""];
for url in urls {
match fetch(url) {
Ok(resp) => println!("OK: {}", resp),
Err(e) => println!("ERR [retryable={}]: {}", e.is_retryable(), e),
}
}
println!("Run `cargo test` to verify all examples.");
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_success() {
assert!(fetch("http://example.com").is_ok());
}
#[test]
fn test_timeout() {
let err = fetch("http://timeout").unwrap_err();
assert!(matches!(err, NetError::Timeout { .. }));
assert!(err.is_retryable());
}
#[test]
fn test_connection_refused() {
let err = fetch("http://refused").unwrap_err();
assert!(matches!(err, NetError::ConnectionRefused(_)));
assert!(err.is_retryable());
}
#[test]
fn test_dns_not_retryable() {
let err = fetch("").unwrap_err();
assert!(matches!(err, NetError::DnsResolutionFailed(_)));
assert!(!err.is_retryable());
}
#[test]
fn test_http_500_retryable() {
let err = fetch("http://500").unwrap_err();
assert!(err.is_retryable());
assert!(!err.is_client_error());
}
#[test]
fn test_http_404_not_retryable() {
let err = fetch("http://404").unwrap_err();
assert!(!err.is_retryable());
assert!(err.is_client_error());
}
#[test]
fn test_retry_success() {
let result = fetch_with_retry("http://example.com", 3);
assert!(result.is_ok());
}
#[test]
fn test_retry_exhausted() {
let result = fetch_with_retry("http://timeout", 2);
assert!(result.is_err());
}
#[test]
fn test_no_retry_on_client_error() {
let result = fetch_with_retry("http://404", 3);
assert!(result.is_err()); // should fail immediately, no retries
}
#[test]
fn test_display() {
let err = NetError::Timeout { seconds: 5.0 };
assert_eq!(err.to_string(), "timeout after 5.0s");
let err = NetError::HttpError { status: 503, body: "Unavailable".into() };
assert_eq!(err.to_string(), "HTTP 503: Unavailable");
}
}
(* 1025: Network Error Classification (Simulated) *)
(* Classifying and handling network-like errors *)
type net_error =
| Timeout of float (* seconds waited *)
| ConnectionRefused of string (* host *)
| DnsResolutionFailed of string
| TlsError of string
| HttpError of int * string (* status code, body *)
let string_of_net_error = function
| Timeout secs -> Printf.sprintf "timeout after %.1fs" secs
| ConnectionRefused host -> Printf.sprintf "connection refused: %s" host
| DnsResolutionFailed host -> Printf.sprintf "DNS failed: %s" host
| TlsError msg -> Printf.sprintf "TLS error: %s" msg
| HttpError (code, body) -> Printf.sprintf "HTTP %d: %s" code body
let is_retryable = function
| Timeout _ -> true
| ConnectionRefused _ -> true
| DnsResolutionFailed _ -> false (* unlikely to change *)
| TlsError _ -> false
| HttpError (code, _) -> code >= 500 (* server errors are retryable *)
(* Simulated network call *)
let fetch url =
if String.length url = 0 then Error (DnsResolutionFailed "empty url")
else if url = "http://timeout" then Error (Timeout 30.0)
else if url = "http://refused" then Error (ConnectionRefused "refused:80")
else if url = "http://500" then Error (HttpError (500, "Internal Server Error"))
else if url = "http://404" then Error (HttpError (404, "Not Found"))
else Ok (Printf.sprintf "response from %s" url)
(* Retry logic *)
let rec fetch_with_retry url retries =
match fetch url with
| Ok _ as result -> result
| Error e when is_retryable e && retries > 0 ->
fetch_with_retry url (retries - 1)
| Error _ as result -> result
let test_errors () =
assert (fetch "http://example.com" = Ok "response from http://example.com");
(match fetch "" with
| Error (DnsResolutionFailed _) -> ()
| _ -> assert false);
(match fetch "http://timeout" with
| Error (Timeout _) -> ()
| _ -> assert false);
Printf.printf " Error classification: passed\n"
let test_retryable () =
assert (is_retryable (Timeout 30.0));
assert (is_retryable (ConnectionRefused "host"));
assert (not (is_retryable (DnsResolutionFailed "host")));
assert (is_retryable (HttpError (503, "Unavailable")));
assert (not (is_retryable (HttpError (404, "Not Found"))));
Printf.printf " Retryable classification: passed\n"
let test_retry () =
(* This will retry but still fail (simulated always-timeout) *)
(match fetch_with_retry "http://timeout" 3 with
| Error (Timeout _) -> ()
| _ -> assert false);
(* Success doesn't need retry *)
assert (fetch_with_retry "http://example.com" 3
= Ok "response from http://example.com");
Printf.printf " Retry logic: passed\n"
let () =
Printf.printf "Testing network errors:\n";
test_errors ();
test_retryable ();
test_retry ();
Printf.printf "โ All tests passed\n"
| Aspect | OCaml | Rust |
|---|---|---|
| Error type | Variant type | Enum |
| Classification | Standalone function | Method on enum |
| Retry guard | `when is_retryable e` | `if e.is_retryable()` |
| Structured data | `HttpError of int * string` | `HttpError { status, body }` |
| Display | `string_of_net_error` | `impl Display` |
| Methods on error | Not idiomatic | Very idiomatic |