Rust Performance Engineering

Copying vs. Borrowing

Choosing between passing a value by Copy (T) and passing by shared reference (&T) depends on the size of T relative to a CPU register and whether T owns heap resources. For small Copy types ($\le 16$ bytes on 64-bit architectures), passing by value is faster than passing &T because it keeps the value directly in CPU registers and eliminates a pointer dereference!

1. Small `Copy` Type Passed by Value (`fn calc(p: Point2D)` where `size_of == 16B`):
   Caller passes `p.x` in register `rdi`/`x0` and `p.y` in `rsi`/`x1`!
   (Zero memory reads; zero pointer indirection!)

2. Small `Copy` Type Passed by Reference (`fn calc(p: &Point2D)`):
   Caller must spill `p` to stack memory, pass its 8-byte address in `rdi`/`x0`,
   and callee must execute memory load instructions (`mov rax, [rdi]`) to read fields!

Size Thresholds for Passing by Value vs Reference

On 64-bit x86_64 and aarch64 calling conventions, up to two 8-byte general-purpose registers (16 bytes total) (or up to four floating-point/vector registers) are used to pass a struct by value:

Type Category & Size Recommended Parameter Type Reason
Primitives & Small Copy Types ($\le 16$ bytes) (u64, f64, (u64, u64), IpAddr, SocketAddrV4) By Value (T) Fits directly in 1–2 CPU registers; passing &u64 adds an unnecessary pointer indirection (clippy::trivially_copy_pass_by_ref).
Large Copy Structs / Arrays ($> 16$–$32$ bytes) ([u8; 256], Matrix4x4<f64>) By Reference (&T) Avoids copying hundreds of bytes onto the stack on every non-inlined call (clippy::large_types_passed_by_value).
Heap-Owning Types (String, Vec<T>, HashMap) By Borrowed Slice/Ref (&str, &[T], &HashMap) unless callee takes ownership Borrowing avoids a deep heap allocation (Clone::clone); only pass owned String/Vec<T> when the function stores the value.

Avoiding Speculative Clones with std::borrow::Cow<'a, B>

When a function inspects a string or byte slice and only modifies it in rare edge cases (for example, escaping HTML characters, normalizing backslashes, or stripping rare control characters), returning String forces a heap allocation on 100% of calls—even when 99% of inputs needed no changes!

Returning std::borrow::Cow<'a, str> (Clone-on-Write) borrows the input &'a str with zero allocation on the common path (Cow::Borrowed), and allocates an owned String (Cow::Owned) only when a modification is actually needed:

use std::borrow::Cow;

/// Normalizes path separators to `/`, allocating a `String` ONLY if `\` is present!
pub fn normalize_slashes(path: &str) -> Cow<'_, str> {
    if path.contains('\\') {
        // Rare path: allocate a new String and replace `\` with `/`
        Cow::Owned(path.replace('\\', "/"))
    } else {
        // Common path (99% of Unix/URL inputs): zero heap allocation!
        Cow::Borrowed(path)
    }
}

fn main() {
    let unix_path = "src/bin/server.rs";
    let win_path = r"src\bin\server.rs";

    let norm_unix = normalize_slashes(unix_path);
    let norm_win = normalize_slashes(win_path);

    assert!(matches!(norm_unix, Cow::Borrowed(_)));
    assert!(matches!(norm_win, Cow::Owned(_)));
    assert_eq!(norm_unix, "src/bin/server.rs");
    assert_eq!(norm_win, "src/bin/server.rs");

    println!("Unix path borrowed without allocation: {}", matches!(norm_unix, Cow::Borrowed(_)));
    println!("Windows path normalized to: {norm_win}");
}
Unix path borrowed without allocation: true
Windows path normalized to: src/bin/server.rs