The Lost Feed

πŸ”¬Weird Science

Rust Structs: Copy vs. Borrow for Small Types

Discover when to pass small Rust structs by copy and when to use borrows. Get clear advice for better Rust code.

1 viewsΒ·6 min readΒ·Jun 14, 2026
Should small Rust structs be passed by-copy or by-borrow? (2019)

When you're writing code in Rust, you often need to pass data around between different parts of your program. For small pieces of data, like simple numbers or short text, this is usually straightforward. But what about small custom data types, called structs? Should you pass them around by making a copy, or by borrowing them?

This is a common question that pops up for many Rust developers. The answer might seem simple at first, but there are some important details to consider. Getting this right can make your code run a little faster and be easier to understand. Let's break down the best practices for handling small structs in Rust.

Understanding

Copy and Borrow in Rust

Before we talk about structs, let's quickly review what copy and borrow mean in Rust. When you *copy

  • a value, you're essentially making a brand new, identical copy of that value. The original value and the new copy are completely separate. Think of it like photocopying a document; you get two independent versions.

Borrowing, on the other hand, is different. When you borrow a value, you're not making a copy. Instead, you're giving another part of your code temporary permission to look at or use the original value. The borrower doesn't own the data; they just have access to it for a limited time. This is like letting a friend read a book you own without giving them a copy.

Rust has strict rules about borrowing to prevent common programming errors like having multiple parts of your code try to change the same data at the same time. This is a big reason why Rust is known for being safe.

The Case for Copying Small Structs

So, when should you choose to copy a small struct? The general rule of thumb in Rust is that if a type is small and cheap to copy, it should probably implement the Copy trait. This trait tells Rust that it's okay to make copies of values of this type freely.

What counts as "small"? Usually, this means structs that contain only other types that are themselves Copy. Think of simple structs made up of integers, booleans, or characters. These types are very fast for the computer to duplicate.

When a struct is marked as Copy, passing it to a function or assigning it to a new variable automatically creates a copy. This can be very convenient. You don't have to worry about accidentally invalidating the original value because you're always working with a fresh copy.

Why Borrowing Might Seem

Like the Default

Many developers coming to Rust from other languages might instinctively think about borrowing everything. In languages like C++ or Java, passing objects by reference (which is similar to borrowing) is often the default way to avoid expensive copying. This is a good habit for many situations.

However, Rust's Copy trait is specifically designed for types that are inexpensive to duplicate. For these types, the cost of copying is so low that it's often less overhead than managing borrows. Managing borrows involves keeping track of who has access to the data and for how long, which can sometimes add a small amount of complexity.

If you have a struct that contains, for example, a String or a Vec (which are not Copy types), then you cannot simply make the struct Copy. In these cases, you would naturally fall back to borrowing.

The Performance Angle: Copy vs.

Borrow Overhead

Let's talk about speed. When you copy a small, Copy-able struct, the computer just duplicates its bits. This is usually a very fast operation, often just a few CPU instructions. The compiler is very good at optimizing these copies.

When you borrow, Rust needs to ensure that the borrowing rules are followed. This involves some checks, especially when you have mutable borrows (where you can change the data). While Rust's borrow checker is incredibly efficient, there's still a tiny bit of overhead involved in managing these checks and lifetimes.

For very small structs that are Copy, the overhead of copying is often less than the overhead of borrowing. This is a key insight that might surprise developers new to Rust.

When to Avoid Copying: Larger or Non-Copy Structs

It's crucial to remember that not all structs should be Copy. The Copy trait is a marker that says "it's safe and cheap to duplicate this." If your struct contains data that is expensive to copy, or data that has special ownership rules (like String or Vec), then it should *not

  • be Copy.

If a struct contains a String, a Vec, a file handle, or a network connection, making a simple bitwise copy would be incorrect or inefficient. For instance, copying a String would only copy the pointer to the string data, not the data itself. This could lead to multiple parts of your code trying to manage the same memory, which is exactly what Rust's ownership system is designed to prevent.

In these cases, you must rely on Rust's borrowing system. You can pass references (& for immutable borrows, &mut for mutable borrows) to these structs. This ensures that only one part of your code can mutate the data at a time, or that multiple parts can read it safely.

Practical

Examples and Guidelines

Let's look at some practical scenarios.

Imagine a struct representing a 2D point:

struct Point { x: i32, y: i32 }

Since i32 is Copy, this Point struct can and should be Copy. Passing Point values around by copy is perfectly fine and often preferred.

Now consider a struct that holds a user's name:

struct UserProfile {
name: String,
id: u64,
}

Because name is a String (which is not Copy), UserProfile cannot be Copy. You would pass references to UserProfile when you need to access its data without taking ownership.

Here’s a simple guideline:

  • If your struct contains only Copy types and is small, make it Copy.
  • *If your struct contains any non-Copy types

  • (like String, Vec, Box, etc.), do not make it Copy. Use borrowing instead.

This approach aligns with Rust's philosophy of explicitness and safety. By making small, simple data types Copy, you gain convenience and potentially a slight performance edge without sacrificing safety.

The Copy

Trait and Trait Bounds

When you define functions that can work with any type that is Copy, you can use trait bounds. For example:

fn process_copyable<T: Copy>(value: T) { /

- ... */ }

This process_copyable function can accept any type T as long as T implements the Copy trait. This makes your code more generic and reusable.

Similarly, if you need to work with types that can be borrowed immutably, you'd use &T or a bound like T: ?Sized.

Understanding these traits helps you write more idiomatic and efficient Rust code. For small, value-like data, embracing Copy is often the best path.

Final

Thoughts on Small Structs

Choosing between passing small structs by copy or by borrow in Rust comes down to understanding the Copy trait and the nature of the data within your struct. For types that are cheap and simple to duplicate, like those composed solely of primitive types or other Copy types, implementing Copy is the idiomatic and often more performant choice.

This allows Rust to handle the duplication automatically and efficiently, reducing the need for explicit borrow management in these specific cases. It makes your code cleaner and can even offer a slight performance benefit by avoiding the overhead associated with borrow checking for these simple data types.

However, always remember that if your struct contains any non-Copy types, such as String or Vec, you must rely on borrowing. Attempting to make such a struct Copy would be a mistake, leading to incorrect behavior or compilation errors. By following these guidelines, you can write more effective and safer Rust code.

How does this make you feel?

Comments

0/2000

Loading comments...