Course contentsShow
← Rust
Lesson 299 of 1,9103. Borrowing and ReferencesFree lesson

Reference-based API Design

Build library interfaces that prioritize borrow-friendly access patterns.

In this lesson, you will learn how to design library interfaces that allow users to pass data efficiently without forcing unnecessary ownership transfers.

When you design a Rust API, your goal is to make it as "borrow-friendly" as possible. Instead of requiring the caller to hand over ownership of an object (which forces them to lose access to it), you should design your functions to accept references whenever possible.

The Problem: Ownership Hostility

Imagine you have a Logger library. If you design your method like this:

struct Logger;
impl Logger {
    fn log(&self, message: String) {
        println!("{}", message);
    }
}

This API is "ownership-hostile." The user must provide a String (which implies they must own the data). If they have a &str or need to use that String later, they are forced to clone it before passing it to your function. This adds unnecessary memory allocation.

The Solution: Designing for Borrowing

To make your API borrow-friendly, use types that represent borrowed data, such as &str instead of String, or &[T] instead of Vec<T>.

impl Logger {
    fn log(&self, message: &str) {
        println!("{}", message);
    }
}

Now, the user can pass a String, a literal &str, or even a slice of a String without worrying about clones. Because you used &str, your API accepts anything that can be viewed as a string slice.

Applying AsRef for Flexibility

If you want to be even more generic, you can use the AsRef trait. This allows your API to accept anything that can be converted into a reference.

fn process_data<T: AsRef<[u8]>>(data: T) {
    let slice = data.as_ref();
    // Now you have a &[u8] slice regardless of the input type
}

By designing your parameters as references or using traits that bridge the gap, you prevent the "cloning tax" and allow your users to keep their data exactly where it is. This is the hallmark of a professional, ergonomic Rust library.

Key Takeaway: Prioritize input types that accept references or view types to keep your API flexible and avoid unnecessary memory allocations for the caller.