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