What are the trade-offs between http::request::Builder::uri and uri_ref for setting request URIs?

uri accepts an owned Uri value and moves it into the builder, which is ergonomic for newly constructed URIs but requires ownership, while uri_ref accepts a borrowed &Uri reference, avoiding allocation and ownership transfer when you already have a Uri that you need to reuse. The choice between them depends on whether you're creating a new URI (where uri is natural) or referencing an existing one (where uri_ref avoids unnecessary allocation or cloning).

The Builder Pattern

use http::request::Builder;
use http::Uri;
 
fn builder_basics() {
    // Request builder uses fluent API
    let request = Builder::new()
        .method("GET")
        .uri("https://example.com/api")
        .body(())
        .unwrap();
    
    // uri() accepts &str and creates a Uri internally
    // This is the most common usage
}

The http::request::Builder provides a fluent interface for constructing HTTP requests.

The uri Method

use http::request::Builder;
use http::Uri;
 
fn uri_method() {
    // uri() accepts anything that can be converted to Uri
    let request = Builder::new()
        .uri("https://example.com/path")
        .body(())
        .unwrap();
    
    // uri() also accepts an owned Uri
    let uri: Uri = "https://example.com/api".parse().unwrap();
    let request = Builder::new()
        .uri(uri)  // Takes ownership of Uri
        .body(())
        .unwrap();
}

uri takes ownership of a Uri value or accepts types that implement Into<Uri>.

The uri_ref Method

use http::request::Builder;
use http::Uri;
 
fn uri_ref_method() {
    let uri: Uri = "https://example.com/api".parse().unwrap();
    
    // uri_ref() borrows the Uri
    let request = Builder::new()
        .uri_ref(&uri)  // Takes &Uri, doesn't take ownership
        .body(())
        .unwrap();
    
    // uri is still valid - we only borrowed it
    println!("URI still usable: {}", uri);
}

uri_ref borrows a Uri reference without taking ownership.

Ownership Transfer with uri

use http::request::Builder;
use http::Uri;
 
fn ownership_transfer() {
    let uri: Uri = "https://example.com/api".parse().unwrap();
    
    // uri() takes ownership
    let request = Builder::new()
        .uri(uri)  // uri is moved here
        .body(())
        .unwrap();
    
    // uri is no longer accessible
    // println!("{}", uri);  // Error: value borrowed after move
}

uri consumes the Uri, making it unavailable after the call.

Borrowing with uri_ref

use http::request::Builder;
use http::Uri;
 
fn borrowing() {
    let uri: Uri = "https://example.com/api".parse().unwrap();
    
    // uri_ref() borrows
    let request = Builder::new()
        .uri_ref(&uri)  // uri is borrowed, not moved
        .body(())
        .unwrap();
    
    // uri is still accessible
    println!("Reusing URI: {}", uri);
    
    // Can use it again
    let request2 = Builder::new()
        .uri_ref(&uri)  // Borrow again
        .body(())
        .unwrap();
}

uri_ref allows reuse of the Uri after setting it on the builder.

Method Signatures

use http::{Uri, request::Builder};
 
// Simplified signatures:
 
// uri takes ownership (or anything Into<Uri>)
// fn uri<T>(self, uri: T) -> Self
// where T: Into<Uri>
 
// uri_ref takes a reference
// fn uri_ref(self, uri: &Uri) -> Self
 
fn signature_differences() {
    // uri accepts:
    // - &str (parsed into Uri)
    // - String (parsed into Uri)
    // - Uri (moved)
    // - Any type implementing Into<Uri>
    
    // uri_ref accepts:
    // - &Uri (borrowed reference)
}

uri accepts owned values or types that convert to Uri; uri_ref only accepts &Uri.

Allocation Behavior

use http::{Uri, request::Builder};
 
fn allocation_comparison() {
    // uri() with &str creates a new Uri internally
    let request1 = Builder::new()
        .uri("https://example.com/path")  // Allocates Uri
        .body(())
        .unwrap();
    
    // uri() with owned Uri - no additional allocation
    let uri: Uri = "https://example.com/api".parse().unwrap();
    let request2 = Builder::new()
        .uri(uri)  // Uses existing Uri, moves it
        .body(())
        .unwrap();
    
    // uri_ref() with &Uri - references existing allocation
    let uri: Uri = "https://example.com/api".parse().unwrap();
    let request3 = Builder::new()
        .uri_ref(&uri)  // No allocation, just reference
        .body(())
        .unwrap();
}

uri_ref avoids allocation by referencing an existing Uri.

Reusing URIs Across Requests

use http::{Uri, request::Builder};
 
fn reuse_uris() {
    let base_uri: Uri = "https://api.example.com/v1".parse().unwrap();
    
    // Build multiple requests using same base URI
    let request1 = Builder::new()
        .uri_ref(&base_uri)  // Reuse base
        .body(())
        .unwrap();
    
    // Can still use base_uri for another request
    let request2 = Builder::new()
        .uri_ref(&base_uri)  // Reuse again
        .body(())
        .unwrap();
    
    // With uri(), would need to clone
    // let request3 = Builder::new()
    //     .uri(base_uri.clone())  // Explicit clone needed
    //     .body(())
    //     .unwrap();
    
    // base_uri still available for further use
    println!("Base URI: {}", base_uri);
}

uri_ref enables efficient reuse of Uri values across multiple requests.

Cloning to Use uri Multiple Times

use http::{Uri, request::Builder};
 
fn cloning_for_uri() {
    let uri: Uri = "https://example.com/api".parse().unwrap();
    
    // If you need to use uri() multiple times with same Uri
    // You must clone first
    
    let request1 = Builder::new()
        .uri(uri.clone())  // Clone to preserve original
        .body(())
        .unwrap();
    
    let request2 = Builder::new()
        .uri(uri.clone())  // Clone again
        .body(())
        .unwrap();
    
    // Original still usable
    let request3 = Builder::new()
        .uri(uri)  // Finally consume
        .body(())
        .unwrap();
}

Using uri multiple times requires cloning; uri_ref borrows instead.

Ergonomics for New URIs

use http::request::Builder;
 
fn ergonomics_new_uris() {
    // When creating URIs inline, uri() is ergonomic
    let request = Builder::new()
        .uri("https://example.com/path?query=value")  // String literal
        .body(())
        .unwrap();
    
    // Equivalent with uri_ref would be:
    let uri: http::Uri = "https://example.com/path?query=value".parse().unwrap();
    let request = Builder::new()
        .uri_ref(&uri)
        .body(())
        .unwrap();
    
    // uri() is more ergonomic for one-off URIs
}

uri is more ergonomic when creating URIs inline from string literals.

Performance Comparison

use http::{Uri, request::Builder};
 
fn performance() {
    // Scenario 1: String literal
    // uri(&str) parses and allocates a new Uri
    let request = Builder::new()
        .uri("https://example.com")  // Parse + allocate
        .body(())
        .unwrap();
    
    // Scenario 2: Owned Uri
    // uri(Uri) moves existing Uri, no additional allocation
    let uri: Uri = "https://example.com".parse().unwrap();
    let request = Builder::new()
        .uri(uri)  // Move, no allocation
        .body(())
        .unwrap();
    
    // Scenario 3: Borrowed Uri
    // uri_ref(&Uri) references existing Uri, no allocation
    let uri: Uri = "https://example.com".parse().unwrap();
    let request = Builder::new()
        .uri_ref(&uri)  // Reference, no allocation
        .body(())
        .unwrap();
    
    // uri_ref is most efficient when Uri already exists
}

uri_ref is most efficient when you have an existing Uri to reference.

Multiple Requests Same URI

use http::{Uri, request::Builder, Method};
 
fn multiple_requests_same_uri() {
    let api_endpoint: Uri = "https://api.example.com/users".parse().unwrap();
    
    // Create multiple requests to same endpoint
    // Using uri_ref avoids cloning
    
    let get_request = Builder::new()
        .method(Method::GET)
        .uri_ref(&api_endpoint)
        .body(())
        .unwrap();
    
    let post_request = Builder::new()
        .method(Method::POST)
        .uri_ref(&api_endpoint)
        .body(())
        .unwrap();
    
    let delete_request = Builder::new()
        .method(Method::DELETE)
        .uri_ref(&api_endpoint)
        .body(())
        .unwrap();
    
    // api_endpoint still usable
    // No clones or allocations for URI reuse
}

uri_ref is ideal for building multiple requests to the same endpoint.

Lifetime Considerations

use http::{Uri, request::Builder};
 
fn lifetime_considerations() {
    // uri_ref creates a dependency on the Uri's lifetime
    let request = {
        let uri: Uri = "https://example.com".parse().unwrap();
        
        let builder = Builder::new()
            .uri_ref(&uri);  // Builder borrows uri
        
        // builder.body(()) would capture &uri
        // Cannot return this - uri doesn't live long enough
        
        builder.body(()).unwrap()
        // uri goes out of scope, but request contains copied URI
    };
    
    // This works because the request owns its own copy of the URI
    // The builder internally clones when body() is called
}

The builder internally copies the URI when the request is finalized.

Builder Internals

use http::{Uri, request::Builder};
 
fn builder_internals() {
    // When you call uri() or uri_ref():
    // - The URI is stored in the builder
    // - When body() is called, the final Request is constructed
    // - The Request owns its own copy of the URI
    
    // uri(): Stored Uri is moved into Request
    // uri_ref(): Uri is cloned into Request
    
    // So even with uri_ref(), the final Request owns its URI
    // The borrow is temporary - only during building
    
    let uri: Uri = "https://example.com".parse().unwrap();
    let request = Builder::new()
        .uri_ref(&uri)  // Borrows uri
        .body(())       // Clones into Request
        .unwrap();
    
    // uri is no longer borrowed after body()
    println!("URI still valid: {}", uri);
    
    // request owns its own copy
}

Both methods result in the request owning its URI; uri_ref borrows temporarily during building.

Type Conversion Flexibility

use http::{Uri, request::Builder};
 
fn type_flexibility() {
    // uri() is flexible - accepts many types
    let r1 = Builder::new()
        .uri("https://example.com")  // &str
        .body(());
    
    let r2 = Builder::new()
        .uri(String::from("https://example.com"))  // String
        .body(());
    
    let uri: Uri = "https://example.com".parse().unwrap();
    let r3 = Builder::new()
        .uri(uri)  // Uri (owned)
        .body(());
    
    // uri_ref() is strict - only &Uri
    let uri: Uri = "https://example.com".parse().unwrap();
    let r4 = Builder::new()
        .uri_ref(&uri)  // Must be &Uri
        .body(());
    
    // This would NOT work:
    // Builder::new().uri_ref("https://example.com")  // Error: expected &Uri
}

uri accepts multiple types; uri_ref strictly requires &Uri.

Common Patterns

use http::{Uri, request::Builder};
 
fn patterns() {
    // Pattern 1: Inline string (use uri)
    let request = Builder::new()
        .uri("https://example.com/api")
        .body(())
        .unwrap();
    
    // Pattern 2: Constructed Uri (use uri)
    let uri = Uri::builder()
        .scheme("https")
        .authority("example.com")
        .path_and_query("/api")
        .build()
        .unwrap();
    let request = Builder::new()
        .uri(uri)  // Consumes constructed URI
        .body(())
        .unwrap();
    
    // Pattern 3: Shared URI (use uri_ref)
    let shared_uri: Uri = "https://api.example.com/endpoint".parse().unwrap();
    
    let request1 = Builder::new().uri_ref(&shared_uri).body(()).unwrap();
    let request2 = Builder::new().uri_ref(&shared_uri).body(()).unwrap();
    // shared_uri still usable
}

Choose based on whether you own the URI or are borrowing it.

Integration with hyper

use http::{Uri, Request, Method};
use hyper::Body;
 
fn hyper_integration() {
    let endpoint: Uri = "https://httpbin.org/get".parse().unwrap();
    
    // Building request with shared endpoint
    let request1 = Request::builder()
        .method(Method::GET)
        .uri_ref(&endpoint)
        .body(Body::empty())
        .unwrap();
    
    let request2 = Request::builder()
        .method(Method::POST)
        .uri_ref(&endpoint)
        .body(Body::from("data"))
        .unwrap();
    
    // endpoint still usable after building both requests
}

uri_ref works well when making multiple requests to the same endpoint.

Error Handling

use http::{Uri, request::Builder};
 
fn error_handling() {
    // Both methods can fail during URI construction
    // But only during the uri() call with string
    
    // uri(&str) can fail to parse
    let result = Builder::new()
        .uri("not a valid uri :: invalid")
        .body(());
    // This succeeds - parsing happens internally
    // Invalid URIs may be accepted (they're validated later)
    
    // uri_ref(&Uri) with pre-parsed URI
    let uri: Uri = "https://example.com".parse().unwrap();
    let result = Builder::new()
        .uri_ref(&uri)
        .body(());
    // Cannot fail due to URI - already validated
}

uri_ref with pre-parsed Uri avoids parsing errors during request building.

Performance Summary

use http::{Uri, request::Builder};
 
fn performance_summary() {
    // | Method | Input | Allocation | Use Case |
    // |--------|-------|------------|----------|
    // | uri(&str) | String literal | Yes (parse) | One-off inline URIs |
    // | uri(Uri) | Owned Uri | No (move) | Created URI, single use |
    // | uri_ref(&Uri) | Borrowed Uri | No (borrow) | Shared URI, multiple uses |
    
    // Memory efficiency ranking:
    // 1. uri_ref(&Uri) - no allocation, no move
    // 2. uri(Uri) - no allocation, moves ownership
    // 3. uri(&str) - allocates new Uri
    
    // Choose based on:
    // - Do you already have a Uri? -> uri or uri_ref
    // - Do you need to reuse it? -> uri_ref
    // - Is it a one-off string? -> uri(&str)
}

Comparison Table

use http::{Uri, request::Builder};
 
fn comparison() {
    // | Aspect | uri | uri_ref |
    // |--------|-----|---------|
    // | Parameter type | T: Into<Uri> | &Uri |
    // | Ownership | Takes ownership | Borrows |
    // | Reusability | Consumes value | Allows reuse |
    // | Allocation | May allocate | No allocation |
    // | String input | Yes (&str, String) | No |
    // | Uri input | Yes (owned) | Yes (borrowed) |
    // | Ergonomics | Better for inline | Better for shared |
    // | Use case | One-off URIs | Shared URIs |
}

Synthesis

Quick reference:

use http::{Uri, Request};
 
fn quick_reference() {
    // uri(): owned, consumes value, ergonomic for strings
    let request1 = Request::builder()
        .uri("https://example.com/api")  // String literal
        .body(())
        .unwrap();
    
    let owned_uri: Uri = "https://example.com".parse().unwrap();
    let request2 = Request::builder()
        .uri(owned_uri)  // Takes ownership
        .body(())
        .unwrap();
    // owned_uri is now consumed
    
    // uri_ref(): borrowed, allows reuse
    let shared_uri: Uri = "https://api.example.com".parse().unwrap();
    let request3 = Request::builder()
        .uri_ref(&shared_uri)  // Borrows
        .body(())
        .unwrap();
    // shared_uri still usable
    
    let request4 = Request::builder()
        .uri_ref(&shared_uri)  // Borrow again
        .body(())
        .unwrap();
}

Key insight: The trade-off between uri and uri_ref centers on ownership semantics. uri is designed for cases where you're providing a URI value to the builder—either inline as a string (which gets parsed) or as an owned Uri that you're transferring ownership of. This is ergonomic and natural for one-off requests. uri_ref is designed for cases where you have an existing Uri that you want to reference without transferring ownership—common when building multiple requests to the same endpoint or when the Uri is shared across multiple parts of your application. The performance difference matters when Uri values are reused: uri_ref simply borrows the reference without allocation, while using uri would require explicit cloning to preserve the original. The builder internally copies the URI when body() is called, so both methods result in the final Request owning its own URI—the borrow from uri_ref is temporary and only exists during the builder chain. Use uri for inline strings and owned Uri values that won't be reused; use uri_ref when you have a shared Uri reference and want to avoid cloning or preserve ownership for later use.