What is the purpose of http::StatusCode::as_u16 vs StatusCode::as_str for HTTP status handling?

HTTP status codes serve as the primary mechanism for conveying request outcomes in HTTP responses. The http::StatusCode type provides two distinct representations: as_u16 returns the numeric code (like 200 or 404), while as_str returns the canonical reason phrase (like "OK" or "Not Found"). These methods serve different purposes: numeric codes are essential for programmatic handling, logging, and interoperation with systems that expect numbers, while reason phrases provide human-readable context for debugging, error messages, and HTTP/1.1 response formatting. Understanding when to use each representation helps build systems that are both machine-processable and human-understandable.

Basic StatusCode Usage

use http::StatusCode;
 
fn basic_usage() {
    let status = StatusCode::OK;
    
    // Numeric representation
    let code: u16 = status.as_u16();
    assert_eq!(code, 200);
    
    // String representation (reason phrase)
    let reason: &str = status.as_str();
    assert_eq!(reason, "OK");
    
    // Display shows "200 OK"
    println!("{}", status); // "200 OK"
    
    // Debug shows more detail
    println!("{:?}", status); // "OK"
}

The two methods provide different views of the same status code.

as_u16 for Numeric Operations

use http::StatusCode;
 
fn numeric_operations() {
    let status = StatusCode::NOT_FOUND;
    
    // Use numeric code for:
    // 1. Programmatic handling
    let code = status.as_u16();
    if code >= 400 && code < 500 {
        println!("Client error: {}", code);
    }
    
    // 2. Classification
    fn classify_status(status: StatusCode) -> &'static str {
        match status.as_u16() {
            100..=199 => "informational",
            200..=299 => "success",
            300..=399 => "redirection",
            400..=499 => "client error",
            500..=599 => "server error",
            _ => "unknown",
        }
    }
    
    // 3. Logging numeric values
    struct LogEntry {
        status_code: u16,
        path: String,
    }
    
    let entry = LogEntry {
        status_code: status.as_u16(),
        path: "/api/users".to_string(),
    };
    
    // 4. Metrics collection
    fn record_metric(status: StatusCode) {
        let code = status.as_u16();
        // Metrics systems often expect numeric values
        // HISTOGRAM("http.status", code);
    }
}

Numeric codes enable programmatic classification and data processing.

as_str for Human-Readable Messages

use http::StatusCode;
 
fn string_operations() {
    let status = StatusCode::INTERNAL_SERVER_ERROR;
    
    // Use reason phrase for:
    // 1. Error messages
    fn error_message(status: StatusCode) -> String {
        format!("Request failed: {} ({})", status.as_str(), status.as_u16())
    }
    
    println!("{}", error_message(status));
    // "Request failed: Internal Server Error (500)"
    
    // 2. HTTP/1.1 response formatting
    fn format_response_line(status: StatusCode) -> String {
        format!("HTTP/1.1 {} {}", status.as_u16(), status.as_str())
    }
    
    let line = format_response_line(StatusCode::CREATED);
    assert_eq!(line, "HTTP/1.1 201 Created");
    
    // 3. User-facing messages
    fn user_message(status: StatusCode) -> &'static str {
        status.as_str()
    }
}

Reason phrases provide human context for status codes.

Understanding Reason Phrases

use http::StatusCode;
 
fn reason_phrases() {
    // Standard status codes have defined reason phrases
    let statuses = vec![
        (StatusCode::OK, "OK"),
        (StatusCode::CREATED, "Created"),
        (StatusCode::NO_CONTENT, "No Content"),
        (StatusCode::MOVED_PERMANENTLY, "Moved Permanently"),
        (StatusCode::FOUND, "Found"),
        (StatusCode::BAD_REQUEST, "Bad Request"),
        (StatusCode::UNAUTHORIZED, "Unauthorized"),
        (StatusCode::FORBIDDEN, "Forbidden"),
        (StatusCode::NOT_FOUND, "Not Found"),
        (StatusCode::INTERNAL_SERVER_ERROR, "Internal Server Error"),
        (StatusCode::BAD_GATEWAY, "Bad Gateway"),
        (StatusCode::SERVICE_UNAVAILABLE, "Service Unavailable"),
    ];
    
    for (status, expected) in statuses {
        assert_eq!(status.as_str(), expected);
    }
    
    // Custom status codes also work
    let custom = StatusCode::from_u16(299).unwrap();
    // Unknown codes return empty string for as_str
    println!("Custom status: '{}' is empty for unknown", custom.as_str());
}

Well-known status codes have canonical reason phrases; unknown codes return empty strings.

Custom Status Codes

use http::StatusCode;
 
fn custom_status_codes() {
    // Create status from u16
    let custom = StatusCode::from_u16(299).unwrap();
    
    // Numeric representation always works
    assert_eq!(custom.as_u16(), 299);
    
    // String representation is empty for unknown codes
    assert_eq!(custom.as_str(), "");
    
    // Common patterns for custom codes
    fn is_custom_status(status: StatusCode) -> bool {
        // Non-standard status codes
        !matches!(status.as_u16(),
            100..=199 |  // Informational
            200..=299 |  // Success
            300..=399 |  // Redirection
            400..=499 |  // Client Error
            500..=599    // Server Error
        )
    }
    
    // WebDAv extensions (200-level)
    let webdav_207 = StatusCode::from_u16(207).unwrap(); // Multi-Status
    let webdav_208 = StatusCode::from_u16(208).unwrap(); // Already Reported
    
    // Unofficial codes
    let unofficial_103 = StatusCode::from_u16(103).unwrap(); // Early Hints
    
    // as_str may have predefined values for some extensions
    println!("207: '{}'", webdav_207.as_str()); // "Multi-Status"
}

StatusCode handles both standard and custom numeric codes gracefully.

Display and Debug Implementations

use http::StatusCode;
 
fn display_debug() {
    let status = StatusCode::NOT_FOUND;
    
    // Display shows "404 Not Found"
    let display = format!("{}", status);
    assert_eq!(display, "404 Not Found");
    
    // Debug shows reason phrase
    let debug = format!("{:?}", status);
    assert_eq!(debug, "NOT_FOUND");
    
    // Both representations include different info
    // Display: full HTTP status line component
    // Debug: constant name
    
    // For comparison
    let created = StatusCode::CREATED;
    println!("Display: {}", created); // "201 Created"
    println!("Debug: {:?}", created);  // "CREATED"
}

Display includes both code and reason; Debug shows the constant name.

Classification Helper Methods

use http::StatusCode;
 
fn classification_helpers() {
    // StatusCode provides helper methods for classification
    let status = StatusCode::OK;
    
    // Check status classes
    assert!(status.is_success());
    assert!(!status.is_client_error());
    assert!(!status.is_server_error());
    
    // Class-based patterns
    fn handle_status(status: StatusCode) {
        if status.is_informational() {
            // 1xx: Continue with request
        } else if status.is_success() {
            // 2xx: Request succeeded
        } else if status.is_redirection() {
            // 3xx: Follow redirect
        } else if status.is_client_error() {
            // 4xx: Fix client request
            println!("Client error: {}", status.as_u16());
        } else if status.is_server_error() {
            // 5xx: Server issue
            println!("Server error: {}", status);
        }
    }
    
    // These methods use as_u16 internally
    // is_success: 200-299
    // is_client_error: 400-499
    // is_server_error: 500-599
}

Helper methods like is_success() are built on numeric ranges.

HTTP Response Formatting

use http::{StatusCode, Response, Version};
use http::header;
 
fn response_formatting() {
    // Building HTTP/1.1 response line
    fn build_status_line(version: Version, status: StatusCode) -> String {
        match version {
            Version::HTTP_09 => format!("{}", status.as_u16()), // No reason in HTTP/0.9
            Version::HTTP_10 => format!("HTTP/1.0 {} {}", status.as_u16(), status.as_str()),
            Version::HTTP_11 => format!("HTTP/1.1 {} {}", status.as_u16(), status.as_str()),
            Version::HTTP_2 => format!(":status: {}", status.as_u16()), // HTTP/2 uses pseudo-headers
            _ => format!("HTTP/1.1 {} {}", status.as_u16(), status.as_str()),
        }
    }
    
    let status_line = build_status_line(Version::HTTP_11, StatusCode::OK);
    assert_eq!(status_line, "HTTP/1.1 200 OK");
    
    // HTTP/2 doesn't use reason phrases
    // Only the numeric code in pseudo-header
    let status = StatusCode::NOT_FOUND;
    // HTTP/2: :status: 404
    println!("HTTP/2 status: {}", status.as_u16());
}

HTTP versions differ in whether they include reason phrases.

Logging and Metrics

use http::StatusCode;
use std::collections::HashMap;
 
fn logging_metrics() {
    // Numeric codes for metrics systems
    fn record_status_metric(status: StatusCode) {
        let code = status.as_u16();
        
        // Prometheus-style metrics
        let metric = format!("http_requests_total{{status=\"{}\"}}", code);
        println!("{}", metric);
        
        // Or histogram buckets
        // HTTP_STATUS_BUCKET.observe(code as f64);
    }
    
    // Structured logging
    #[derive(serde::Serialize)]
    struct LogRecord {
        status_code: u16,
        status_reason: &'static str,
        timestamp: u64,
    }
    
    fn log_response(status: StatusCode) {
        let record = LogRecord {
            status_code: status.as_u16(),
            status_reason: status.as_str(),
            timestamp: 1234567890,
        };
        println!("{}", serde_json::to_string(&record).unwrap());
    }
    
    // Aggregation by numeric code
    let mut status_counts: HashMap<u16, u32> = HashMap::new();
    for status in [StatusCode::OK, StatusCode::OK, StatusCode::NOT_FOUND] {
        *status_counts.entry(status.as_u16()).or_insert(0) += 1;
    }
    assert_eq!(status_counts.get(&200), Some(&2));
    assert_eq!(status_counts.get(&404), Some(&1));
}

Numeric codes are essential for metrics aggregation; reason phrases aid human interpretation.

Error Handling Patterns

use http::StatusCode;
 
fn error_handling() {
    // Matching on numeric values
    fn handle_response(status: StatusCode) -> Result<String, String> {
        match status.as_u16() {
            200 => Ok("Success".to_string()),
            201 => Ok("Created".to_string()),
            204 => Ok("No Content".to_string()),
            400 => Err(format!("Bad Request: {}", status.as_str())),
            401 => Err(format!("Unauthorized: {}", status.as_str())),
            403 => Err(format!("Forbidden: {}", status.as_str())),
            404 => Err(format!("Not Found: {}", status.as_str())),
            500..=599 => Err(format!("Server Error {}: {}", status.as_u16(), status.as_str())),
            _ => Err(format!("Unknown status: {}", status.as_u16())),
        }
    }
    
    // Using built-in classification
    fn classify_error(status: StatusCode) -> &'static str {
        if status.is_client_error() {
            "Fix your request"
        } else if status.is_server_error() {
            "Try again later"
        } else if status.is_success() {
            "All good"
        } else {
            "Unexpected"
        }
    }
    
    // Detailed error with both representations
    fn detailed_error(status: StatusCode, message: &str) -> String {
        format!(
            "HTTP {}: {} - {}",
            status.as_u16(),
            status.as_str(),
            message
        )
    }
    
    let error = detailed_error(StatusCode::BAD_GATEWAY, "Upstream timeout");
    assert_eq!(error, "HTTP 502: Bad Gateway - Upstream timeout");
}

Combining numeric and string representations provides comprehensive error information.

Status Code Constants

use http::StatusCode;
 
fn status_constants() {
    // StatusCode provides constants for common codes
    let ok = StatusCode::OK;                    // 200
    let created = StatusCode::CREATED;          // 201
    let accepted = StatusCode::ACCEPTED;        // 202
    let no_content = StatusCode::NO_CONTENT;    // 204
    
    let moved = StatusCode::MOVED_PERMANENTLY;  // 301
    let found = StatusCode::FOUND;              // 302
    let see_other = StatusCode::SEE_OTHER;      // 303
    let not_modified = StatusCode::NOT_MODIFIED; // 304
    
    let bad_request = StatusCode::BAD_REQUEST;   // 400
    let unauthorized = StatusCode::UNAUTHORIZED; // 401
    let forbidden = StatusCode::FORBIDDEN;       // 403
    let not_found = StatusCode::StatusCode::NOT_FOUND; // 404
    
    let internal = StatusCode::INTERNAL_SERVER_ERROR; // 500
    let bad_gateway = StatusCode::BAD_GATEWAY;  // 502
    let unavailable = StatusCode::SERVICE_UNAVAILABLE; // 503
    
    // These constants avoid typos and provide type safety
    // Compare:
    // let status = 200; // Could be any number
    // let status = StatusCode::OK; // Clearly a status code
}

Type-safe constants prevent typos and make intent clear.

Converting Between Representations

use http::StatusCode;
 
fn conversions() {
    // From u16
    let status = StatusCode::from_u16(200).unwrap();
    assert_eq!(status, StatusCode::OK);
    
    // From u16 can fail for invalid codes
    let invalid = StatusCode::from_u16(99);
    assert!(invalid.is_err());
    
    // Valid range: 100-999
    assert!(StatusCode::from_u16(99).is_err());
    assert!(StatusCode::from_u16(100).is_ok());
    assert!(StatusCode::from_u16(999).is_ok());
    assert!(StatusCode::from_u16(1000).is_err());
    
    // To u16 via as_u16
    let code: u16 = StatusCode::OK.as_u16();
    
    // Parse from string
    let from_str: StatusCode = "200".parse().unwrap();
    assert_eq!(from_str, StatusCode::OK);
    
    // Reason phrase strings also work for known codes
    let from_reason: StatusCode = "OK".parse().unwrap();
    assert_eq!(from_reason, StatusCode::OK);
}

StatusCode supports parsing from strings and conversion from u16 with validation.

HTTP/2 Considerations

use http::StatusCode;
 
fn http2_considerations() {
    // HTTP/2 doesn't transmit reason phrases
    // Only the numeric status code is sent
    
    fn build_http2_headers(status: StatusCode) -> Vec<(&'static str, String)> {
        // HTTP/2 uses :status pseudo-header with numeric code
        vec![
            (":status", status.as_u16().to_string()),
            // Reason phrase is NOT included in HTTP/2
        ]
    }
    
    let headers = build_http2_headers(StatusCode::OK);
    assert_eq!(headers[0], (":status", "200".to_string()));
    
    // HTTP/1.1 includes reason phrase
    fn build_http1_status_line(status: StatusCode) -> String {
        format!("HTTP/1.1 {} {}", status.as_u16(), status.as_str())
    }
    
    let line = build_http1_status_line(StatusCode::OK);
    assert_eq!(line, "HTTP/1.1 200 OK");
}

HTTP/2 uses only numeric codes; reason phrases are HTTP/1.1-specific.

Default and Common Values

use http::StatusCode;
 
fn defaults() {
    // Default status code is 200 OK
    let default: StatusCode = Default::default();
    assert_eq!(default, StatusCode::OK);
    
    // Common default patterns
    fn success_response() -> StatusCode {
        StatusCode::OK // Most common success
    }
    
    fn created_response() -> StatusCode {
        StatusCode::CREATED // Resource created
    }
    
    fn no_content_response() -> StatusCode {
        StatusCode::NO_CONTENT // Success with no body
    }
    
    fn bad_request(message: &str) -> (StatusCode, String) {
        (StatusCode::BAD_REQUEST, message.to_string())
    }
    
    fn not_found(resource: &str) -> (StatusCode, String) {
        (StatusCode::NOT_FOUND, format!("{} not found", resource))
    }
    
    fn internal_error() -> StatusCode {
        StatusCode::INTERNAL_SERVER_ERROR
    }
}

StatusCode::OK is the default, reflecting the most common response.

Real-World Example: Web Framework Handler

use http::StatusCode;
use http::header;
 
struct HttpResponse {
    status: StatusCode,
    headers: Vec<(&'static str, String)>,
    body: Option<String>,
}
 
impl HttpResponse {
    fn new(status: StatusCode) -> Self {
        HttpResponse {
            status,
            headers: Vec::new(),
            body: None,
        }
    }
    
    fn with_body(status: StatusCode, body: String) -> Self {
        HttpResponse {
            status,
            headers: vec![("Content-Type", "application/json".to_string())],
            body: Some(body),
        }
    }
    
    fn to_http1_1(&self) -> String {
        let mut response = format!(
            "HTTP/1.1 {} {}\r\n",
            self.status.as_u16(),
            self.status.as_str()
        );
        
        for (name, value) in &self.headers {
            response.push_str(&format!("{}: {}\r\n", name, value));
        }
        
        if let Some(body) = &self.body {
            response.push_str(&format!("Content-Length: {}\r\n", body.len()));
            response.push_str("\r\n");
            response.push_str(body);
        } else {
            response.push_str("\r\n");
        }
        
        response
    }
}
 
fn handle_request(path: &str) -> HttpResponse {
    match path {
        "/health" => HttpResponse::new(StatusCode::OK),
        "/api/users" => HttpResponse::with_body(
            StatusCode::OK,
            r#"[{"id": 1, "name": "Alice"}]"#.to_string()
        ),
        "/api/users/1" => HttpResponse::with_body(
            StatusCode::OK,
            r#"{"id": 1, "name": "Alice"}"#.to_string()
        ),
        _ => HttpResponse::with_body(
            StatusCode::NOT_FOUND,
            r#"{"error": "Not found"}"#.to_string()
        ),
    }
}

Web frameworks use both numeric and string representations when formatting responses.

Real-World Example: Status Logging Middleware

use http::StatusCode;
 
struct AccessLog {
    method: String,
    path: String,
    status_code: u16,
    status_reason: &'static str,
    duration_ms: u64,
}
 
impl AccessLog {
    fn to_json(&self) -> String {
        format!(
            r#"{{"method":"{}","path":"{}","status":{},"reason":"{}","duration":{}}}"#,
            self.method, self.path, self.status_code, self.status_reason, self.duration_ms
        )
    }
    
    fn to_common_log_format(&self) -> String {
        // Common Log Format: host ident authuser date request status bytes
        format!(
            "- - - [timestamp] \"{} {}\" {} -",
            self.method, self.path, self.status_code
        )
    }
}
 
fn logging_middleware(status: StatusCode, method: &str, path: &str, duration_ms: u64) {
    let log = AccessLog {
        method: method.to_string(),
        path: path.to_string(),
        status_code: status.as_u16(),
        status_reason: status.as_str(),
        duration_ms,
    };
    
    // Structured logging for log aggregation
    println!("{}", log.to_json());
    
    // Human-readable for console
    println!("[{}] {} {} - {} ({})", 
        log.duration_ms, log.method, log.path, log.status_code, log.status_reason);
}
 
fn example_logging() {
    logging_middleware(StatusCode::OK, "GET", "/api/users", 15);
    // Output: [15] GET /api/users - 200 (OK)
    
    logging_middleware(StatusCode::NOT_FOUND, "POST", "/api/unknown", 3);
    // Output: [3] POST /api/unknown - 404 (Not Found)
}

Logs benefit from both numeric codes (for parsing) and reason phrases (for human readers).

Synthesis

Method comparison:

Method Return Type Use Case
as_u16() u16 Programmatic handling, metrics, HTTP/2
as_str() &'static str Human-readable messages, HTTP/1.1, debugging

When to use each:

Scenario Preferred Method Reason
Metrics collection as_u16() Numeric aggregation
Status classification as_u16() Range-based matching
HTTP/2 responses as_u16() HTTP/2 doesn't use reason phrases
Error messages as_str() Human context
HTTP/1.1 response line Both "200 OK" format
Log files Both Machine parseable + readable

Status code ranges:

Range Class Example Codes
100-199 Informational 100 Continue, 101 Switching Protocols
200-299 Success 200 OK, 201 Created, 204 No Content
300-399 Redirection 301 Moved Permanently, 304 Not Modified
400-499 Client Error 400 Bad Request, 404 Not Found
500-599 Server Error 500 Internal Server Error, 503 Unavailable

Key insight: The as_u16() and as_str() methods serve complementary purposes in HTTP status handling. The numeric representation (as_u16()) is essential for programmatic processing—classifying responses, aggregating metrics, building HTTP/2 headers, and routing error handling logic. The string representation (as_str()) provides the canonical reason phrase defined in HTTP specifications, adding human-readable context for error messages, log files, and HTTP/1.1 response formatting. The http::StatusCode type gives you both representations with type safety, ensuring you never accidentally use an invalid status code. For comprehensive error information, combine both: format!("HTTP {}: {}", status.as_u16(), status.as_str()) produces "HTTP 404: Not Found", providing both the machine-processable code and human context in a single message.