Borrowing

2 Posts

Library API Design, First Layer

5 minute

In C library APIs, one pointer can mean many different things:

int parse_packet(const uint8_t *data, size_t len, struct Packet *out);
void normalize(uint8_t *data, size_t len);
void consume_buffer(uint8_t *data, size_t len);
uint8_t *make_packet(size_t *out_len);

These signatures all involve pointers, but the API meaning differs:

  • read-only input
  • in-place mutation
  • output result
  • taking over a caller-owned resource
  • creating a new resource for the caller

The first layer of Rust API design is making those intentions visible in the type.

Read-Only Input: Borrow

C read-only buffer:

uint32_t checksum(const uint8_t *data, size_t len);

Rust uses a slice:

fn checksum(data: &[u8]) -> u32 {
    data.iter().map(|b| *b as u32).sum()
}

&[u8] means the function temporarily reads bytes. It does not own the memory and cannot mutate it.

Read More

Borrowing: References Are Not Plain Pointers

5 minute

The previous article covered ownership: who owns, who frees.

Most functions do not want to take over a resource. They only want to inspect data temporarily, or mutate it temporarily. In C, this usually means passing a pointer:

void inspect(const uint8_t *buf, size_t len);
void update(uint8_t *buf, size_t len);

In Rust, this is borrowing:

fn inspect(buf: &[u8]) {}
fn update(buf: &mut [u8]) {}

Borrowing is not just “Rust pointer syntax.” It is access permission: temporarily read or write without taking ownership.

This article covers the first engineering meaning of &T and &mut T, without expanding lifetime annotations yet.

Read More