What is the difference between http::Request::new and Request::builder for constructing HTTP requests programmatically?

http::Request::new creates a request directly from a body with default method (GET) and URI (/), requiring manual header insertion after construction, while Request::builder provides a fluent API for incrementally setting method, URI, headers, and version before constructing the final request. Request::new is a simple constructor best for minimal requests, whereas Request::builder is a builder pattern that enables method chaining and validates parts before construction. The builder returns a Result because it can fail (e.g., invalid header name), while new returns a Request directly because it only accepts already-valid components.

Basic Request::new Usage

use http::{Request, Method, Uri, HeaderMap, HeaderName, HeaderValue};
 
fn basic_new() {
    // Create request with just a body
    let request: Request<String> = Request::new("Hello".to_string());
    
    // Defaults:
    // Method: GET
    // URI: /
    // Version: HTTP/1.1
    // Headers: empty
    
    assert_eq!(request.method(), Method::GET);
    assert_eq!(request.uri(), "/");
    assert_eq!(request.version(), http::Version::HTTP_11);
    assert_eq!(request.headers().len(), 0);
}

Request::new creates a request with minimal configuration and defaults.

Request::builder Basic Usage

use http::{Request, Method};
 
fn basic_builder() {
    // Create request using builder
    let request: Request<String> = Request::builder()
        .method(Method::POST)
        .uri("/api/users")
        .header("Content-Type", "application/json")
        .body(r#"{"name": "Alice"}"#.to_string())
        .unwrap();
    
    assert_eq!(request.method(), Method::POST);
    assert_eq!(request.uri(), "/api/users");
    assert_eq!(request.headers().get("Content-Type").unwrap(), "application/json");
}

Request::builder() returns a Builder that chains method calls before finalizing with .body().

Comparison: Simplicity vs Flexibility

use http::{Request, Method, HeaderMap};
 
fn comparison() {
    // Request::new: Simple, minimal
    let simple: Request<()> = Request::new(());
    // Only sets body, everything else is default
    
    // Request::builder: Flexible, chainable
    let built: Request<String> = Request::builder()
        .method(Method::POST)
        .uri("/api/data")
        .header("Authorization", "Bearer token")
        .header("Content-Type", "application/json")
        .version(http::Version::HTTP_2)
        .body("data".to_string())
        .unwrap();
    
    // Request::new requires post-hoc modification for anything but body
    let mut manual: Request<()> = Request::new(());
    manual.headers_mut().insert(
        http::header::CONTENT_TYPE,
        http::HeaderValue::from_static("text/plain"),
    );
    *manual.method_mut() = Method::POST;
}

Request::new is simpler but requires mutation for configuration; builder sets everything in one chain.

Request::new with Explicit Components

use http::{Request, Method, Uri, HeaderMap, HeaderName, HeaderValue};
 
fn new_with_components() {
    // Request::new takes just the body
    // To set method/uri, modify after construction
    
    let mut request: Request<String> = Request::new("body".to_string());
    
    // Modify method
    *request.method_mut() = Method::POST;
    
    // Modify URI
    *request.uri_mut() = Uri::from_static("/api/endpoint");
    
    // Add headers
    request.headers_mut().insert(
        HeaderName::from_static("content-type"),
        HeaderValue::from_static("application/json"),
    );
    
    // This approach requires:
    // 1. Creating request
    // 2. Mutating each component separately
    // 3. No validation until headers are inserted
}

Request::new requires explicit mutation to set non-default components.

Request::builder Validation

use http::Request;
 
fn builder_validation() {
    // Builder validates during construction
    let valid = Request::builder()
        .header("Content-Type", "application/json")
        .body(())
        .unwrap();  // Success
    
    // Invalid header name causes error
    let invalid = Request::builder()
        .header("Invalid Header Name", "value")  // Space not allowed
        .body(());
    
    assert!(invalid.is_err());
    
    // Invalid URI causes error
    let invalid_uri = Request::builder()
        .uri("not a valid uri ::::")
        .body(());
    
    assert!(invalid_uri.is_err());
}

Builder returns Result because it validates headers, URI, and other components during construction.

Request::new Never Fails

use http::Request;
 
fn new_never_fails() {
    // Request::new always succeeds
    // No validation on body content
    let request: Request<String> = Request::new("anything".to_string());
    
    // The body type can be anything
    let unit_request: Request<()> = Request::new(());
    let bytes_request: Request<Vec<u8>> = Request::new(vec
![0, 1, 2]);
    let json_request: Request<String> = Request::new(r#"{"key": "value"}"#.to_string());
    
    // No Result, no unwrap needed
    // But also no headers, specific method, or custom URI
}

Request::new never returns an error because it doesn't validate anything beyond accepting the body.

Header Handling Differences

use http::{Request, HeaderMap, HeaderName, HeaderValue};
 
fn header_handling() {
    // Request::new + manual headers
    let mut request1: Request<()> = Request::new(());
    request1.headers_mut().insert(
        HeaderName::from_static("authorization"),
        HeaderValue::from_static("Bearer token"),
    );
    // Requires HeaderName and HeaderValue types
    
    // Request::builder with string headers
    let request2: Request<()> = Request::builder()
        .header("Authorization", "Bearer token")
        .header("Content-Type", "application/json")
        .body(())
        .unwrap();
    // Builder parses strings into HeaderName/HeaderValue
    
    // Builder handles parsing errors
    let invalid = Request::builder()
        .header("Invalid Name", "value")
        .body(());
    assert!(invalid.is_err());
    
    // Manual insertion would panic or require error handling
    let mut request3: Request<()> = Request::new(());
    // This would compile but panic at runtime with invalid header:
    // request3.headers_mut().insert(
    //     HeaderName::from_static("Invalid Name"),  // Panic!
    //     HeaderValue::from_static("value"),
    // );
}

Builder parses header strings and returns errors; Request::new requires typed headers.

Method and URI Setting

use http::{Request, Method, Uri};
 
fn method_and_uri() {
    // Request::new approach
    let mut request1: Request<()> = Request::new(());
    *request1.method_mut() = Method::POST;
    *request1.uri_mut() = Uri::from_static("/api/users");
    // Three separate operations
    
    // Request::builder approach
    let request2: Request<()> = Request::builder()
        .method(Method::POST)
        .uri("/api/users")
        .body(())
        .unwrap();
    // Single chain, single operation
    
    // Builder also accepts strings that are parsed
    let request3: Request<()> = Request::builder()
        .method("POST")  // Parsed to Method
        .uri("/api/users")  // Parsed to Uri
        .body(())
        .unwrap();
    
    // Builder validates method
    let invalid = Request::builder()
        .method("INVALID METHOD")
        .body(());
    assert!(invalid.is_err());
}

Builder parses strings for method and URI; Request::new requires direct mutation.

Version Setting

use http::{Request, Version};
 
fn version_setting() {
    // Request::new: defaults to HTTP/1.1
    let request1: Request<()> = Request::new(());
    assert_eq!(request1.version(), Version::HTTP_11);
    
    // Modify version after construction
    let mut request2: Request<()> = Request::new(());
    *request2.version_mut() = Version::HTTP_2;
    
    // Request::builder: set version in chain
    let request3: Request<()> = Request::builder()
        .version(Version::HTTP_2)
        .body(())
        .unwrap();
    
    assert_eq!(request3.version(), Version::HTTP_2);
}

Both require explicit setting for non-HTTP/1.1; builder does it inline.

Type Inference and Body Types

use http::Request;
 
fn body_types() {
    // Request::new: body type is explicit
    let string_request: Request<String> = Request::new("body".to_string());
    let vec_request: Request<Vec<u8>> = Request::new(vec
![0u8; 100]);
    let unit_request: Request<()> = Request::new(());
    
    // Body type inferred from new() argument
    
    // Request::builder: body type from .body() argument
    let string_request: Request<String> = Request::builder()
        .body("body".to_string())
        .unwrap();
    
    let vec_request: Request<Vec<u8>> = Request::builder()
        .body(vec
![0u8; 100])
        .unwrap();
    
    // Builder can be reused with different body types
    let builder = Request::builder()
        .method(Method::POST)
        .uri("/api");
    
    let req1: Request<String> = builder.clone().body("text".to_string()).unwrap();
    let req2: Request<Vec<u8>> = builder.clone().body(vec
![0, 1, 2]).unwrap();
}

Both approaches infer body type from the argument; builder allows template reuse.

Builder Reusability

use http::{Request, Method};
 
fn builder_reuse() {
    // Create a builder template
    let base_builder = Request::builder()
        .method(Method::POST)
        .header("Content-Type", "application/json")
        .header("Authorization", "Bearer token");
    
    // Reuse with different bodies
    let request1: Request<String> = base_builder.clone()
        .uri("/api/users")
        .body(r#"{"name": "Alice"}"#.to_string())
        .unwrap();
    
    let request2: Request<String> = base_builder.clone()
        .uri("/api/posts")
        .body(r#"{"title": "Hello"}"#.to_string())
        .unwrap();
    
    // Both have same method and headers
    assert_eq!(request1.method(), Method::POST);
    assert_eq!(request2.method(), Method::POST);
    assert_eq!(
        request1.headers().get("Authorization"),
        request2.headers().get("Authorization")
    );
}

Builder can be cloned and reused with different URIs or bodies.

Request Parts Access

use http::Request;
 
fn parts_access() {
    // Both approaches produce the same Request type
    let request1: Request<String> = Request::new("body".to_string());
    let request2: Request<String> = Request::builder()
        .body("body".to_string())
        .unwrap();
    
    // Both can be deconstructed into parts
    let (parts1, body1) = request1.into_parts();
    let (parts2, body2) = request2.into_parts();
    
    // Parts include method, uri, version, headers
    assert_eq!(parts1.method, http::Method::GET);
    assert_eq!(parts2.method, http::Method::GET);
    
    // Bodies are the same
    assert_eq!(body1, "body");
    assert_eq!(body2, "body");
}

Both produce the same Request type with identical access to parts.

Error Handling Strategies

use http::{Request, header::{self, InvalidHeaderValue}};
 
fn error_handling() {
    // Request::new: No errors during construction
    // Errors come from header insertion
    let mut request: Request<()> = Request::new(());
    
    // HeaderValue::from_str can fail
    match HeaderValue::from_str("invalid\nvalue") {
        Ok(value) => {
            request.headers_mut().insert(header::CONTENT_TYPE, value);
        }
        Err(e) => {
            eprintln!("Invalid header value: {}", e);
        }
    }
    
    // Request::builder: Errors during .body()
    let result = Request::builder()
        .header("Invalid\nHeader", "value")
        .body(());
    
    match result {
        Ok(request) => {
            // Use request
        }
        Err(e) => {
            eprintln!("Failed to build request: {}", e);
        }
    }
    
    // Builder collects all errors, returns on .body()
}

Request::new requires error handling per header; builder collects errors at the end.

Use Case: Simple GET Requests

use http::Request;
 
fn simple_get() {
    // Request::new is ideal for simple GET requests
    // Default method is GET, default URI is /
    
    let request: Request<()> = Request::new(());
    
    // This is equivalent to:
    // GET / HTTP/1.1
    // (no headers)
    // (no body)
    
    // For a simple health check:
    let health_check: Request<()> = Request::new(());
    // Just use the request, all defaults are fine
    
    // Builder would be overkill:
    let same_request: Request<()> = Request::builder()
        .body(())
        .unwrap();
    // More verbose for the same result
}

Request::new is simpler for minimal requests with defaults.

Use Case: Complex API Requests

use http::{Request, Method, header};
 
fn complex_api_request() {
    // Builder is better for requests with many components
    
    let request: Request<String> = Request::builder()
        .method(Method::POST)
        .uri("https://api.example.com/v1/users")
        .version(http::Version::HTTP_2)
        .header(header::CONTENT_TYPE, "application/json")
        .header(header::AUTHORIZATION, "Bearer token123")
        .header(header::ACCEPT, "application/json")
        .header("X-Request-ID", "abc-123")
        .header("X-API-Key", "secret-key")
        .body(r#"{"name": "Alice", "email": "alice@example.com"}"#.to_string())
        .unwrap();
    
    // Compare to Request::new approach:
    let mut request2: Request<String> = Request::new(
        r#"{"name": "Alice", "email": "alice@example.com"}"#.to_string()
    );
    *request2.method_mut() = Method::POST;
    *request2.uri_mut() = "https://api.example.com/v1/users".parse().unwrap();
    *request2.version_mut() = http::Version::HTTP_2;
    request2.headers_mut().insert(header::CONTENT_TYPE, "application/json".parse().unwrap());
    request2.headers_mut().insert(header::AUTHORIZATION, "Bearer token123".parse().unwrap());
    // ... more headers
}

Builder is more ergonomic for complex requests with many headers.

Performance Considerations

use http::Request;
 
fn performance() {
    // Request::new: Minimal allocation, direct construction
    // Creates Request directly from body
    // No intermediate builder state
    
    let request: Request<()> = Request::new(());
    // Fast path, minimal overhead
    
    // Request::builder: Creates Builder, then builds Request
    // Builder stores method, uri, headers, version
    // .body() creates the final Request
    
    let request: Request<()> = Request::builder()
        .header("A", "1")
        .header("B", "2")
        .header("C", "3")
        .body(())
        .unwrap();
    
    // Overhead:
    // 1. Builder struct creation
    // 2. Header parsing
    // 3. Validation
    // 4. Result unwrapping
    
    // For hot paths with simple requests, Request::new is faster
    // For complex requests, builder overhead is negligible
}

Request::new has less overhead; builder has validation and parsing costs.

Memory Layout

use http::Request;
 
fn memory_layout() {
    // Request::new creates Request directly
    // Request<T> contains:
    // - Method (small, inline)
    // - Uri (small struct, may have string allocation)
    // - Version (enum, small)
    // - HeaderMap (hashmap, allocation)
    // - Extensions (small)
    // - Body (T)
    
    // Builder stores:
    // - Option<Method>
    // - Option<Uri>
    // - Option<Version>
    // - HeaderMap
    // - Option<Extensions>
    
    // Then .body() creates Request<T> from these
    
    // Request::new is more direct:
    // - Creates HeaderMap (empty)
    // - Sets defaults
    // - Returns Request
    
    // Builder is indirect:
    // - Creates Builder
    // - Stores options
    // - .body() creates Request from stored options
}

Request::new is a direct constructor; builder has intermediate state.

When to Use Each

use http::{Request, Method};
 
fn usage_guidelines() {
    // Use Request::new when:
    // 1. You only need to set the body
    // 2. Defaults (GET, /, HTTP/1.1) are acceptable
    // 3. You're in a hot path and need minimal overhead
    // 4. You have pre-validated components
    
    let simple: Request<()> = Request::new(());
    let with_body: Request<String> = Request::new("data".to_string());
    
    // Use Request::builder when:
    // 1. You need to set method, URI, or headers
    // 2. You want validation during construction
    // 3. You want method chaining for readability
    // 4. You're building requests from configuration
    // 5. You want to reuse a template
    
    let complex: Request<String> = Request::builder()
        .method(Method::POST)
        .uri("/api/data")
        .header("Authorization", "Bearer token")
        .body("payload".to_string())
        .unwrap();
    
    // Template reuse:
    let template = Request::builder()
        .method(Method::POST)
        .header("Authorization", "Bearer token");
    
    let req1 = template.clone().uri("/api/users").body("{}".to_string()).unwrap();
    let req2 = template.clone().uri("/api/posts").body("{}".to_string()).unwrap();
}

Choose based on complexity, validation needs, and reusability requirements.

Synthesis

Key differences:

Aspect Request::new Request::builder
Configuration Body only Method, URI, headers, version
Defaults GET, /, HTTP/1.1 Same defaults
Error handling No errors Returns Result
Headers Post-hoc insertion Inline with builder
Validation None Validates headers, URI, method
Readability Simple Chainable, declarative
Performance Minimal overhead Builder overhead
Reusability No template Builder can be cloned

When to use Request::new:

// Simple requests with defaults
let request: Request<()> = Request::new(());
 
// When you have pre-validated components
let mut request: Request<String> = Request::new(body);
*request.method_mut() = validated_method;
*request.uri_mut() = validated_uri;
 
// Hot paths where overhead matters
for item in items {
    let request: Request<String> = Request::new(item);
    // send request
}

When to use Request::builder:

// Complex requests with multiple components
let request = Request::builder()
    .method(Method::POST)
    .uri("/api/users")
    .header("Authorization", "Bearer token")
    .header("Content-Type", "application/json")
    .body(json_body)
    .unwrap();
 
// Building from configuration
let request = Request::builder()
    .method(config.method)
    .uri(&config.uri)
    .headers(config.headers)
    .body(config.body)
    .unwrap();
 
// Template reuse
let base = Request::builder().method(Method::POST);
for endpoint in endpoints {
    let request = base.clone().uri(&endpoint).body(body.clone()).unwrap();
}

Key insight: Request::new is a direct constructor that creates a request with just a body, suitable for simple cases or when you want maximum control and minimal overhead. Request::builder is a builder pattern that provides validation, method chaining, and template reuse, suitable for complex requests where readability and correctness matter more than raw performance. The builder returns a Result because it validates during construction, while new never fails because it accepts the body directly and uses defaults for everything else.