<!-- mobian-agent-page publisher="dailydev" canonical="https://daily.dev/posts/practical-rust-api-design-qwmqrnfyi" -->

---
title: Practical Rust API Design | daily.dev
description: A deep dive into designing ergonomic, hard-to-misuse Rust APIs through local reasoning. Covers encoding relationships in function signatures (lifetimes,...
canonical: https://daily.dev/posts/practical-rust-api-design-qwmqrnfyi
twitter:card: summary_large_image
twitter:site: @dailydotdev
og:type: website
og:site_name: daily.dev
og:title: Practical Rust API Design | daily.dev
og:description: A deep dive into designing ergonomic, hard-to-misuse Rust APIs through local reasoning. Covers encoding relationships in function signatures (lifetimes,...
og:url: https://daily.dev/posts/practical-rust-api-design-qwmqrnfyi
og:image: https://api.daily.dev/og/posts/qWMqrnfYI.png
og:image:alt: Practical Rust API Design
og:image:width: 1200
og:image:height: 630
og:locale: en
---

> ## Documentation Index
> Fetch the complete documentation index at: https://daily.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Practical Rust API Design

**[corrode](https://daily.dev/sources/corrode-blog)** · 16 min read · 26 upvotes · 2 comments

## Summary

A deep dive into designing ergonomic, hard-to-misuse Rust APIs through local reasoning. Covers encoding relationships in function signatures (lifetimes, borrows), being cautious with Deref coercion and implicit convenience, naming generics and impl Trait purposefully, extending ownership concepts beyond memory to file descriptors and lock guards, documenting unsafe fn vs unsafe block responsibilities, choosing between panics and Result/Option for error handling, and using #[non_exhaustive] enums thoughtfully (illustrated by the http-types and http crate StatusCode case studies) to manage API evolution and Hyrum's Law.

## Full article

daily.dev links to this article rather than hosting it. Read it at the original source: <https://corrode.dev/blog/practical-rust-api-design>

## Questions this post answers

### What's the difference between an unsafe fn and an unsafe block in Rust?

An unsafe fn declares safety conditions that its caller must establish before calling it, shifting responsibility to the caller, while an unsafe block marks where the person writing the code asserts that the safety conditions of the unsafe operations inside have already been satisfied. Both should carry safety comments explaining why an operation is safe, not just that it is safe.

_daily.dev surfaces practical writing like this for developers refining how they reason about unsafe Rust boundaries._

### Why use BorrowedFd instead of RawFd in a Rust function signature?

BorrowedFd guarantees the file descriptor remains open for the duration of the borrow, letting the compiler check that the owning File outlives any use of the descriptor, which prevents time-of-check-to-time-of-use bugs. RawFd is just a bare integer alias for c_int with no guarantee the descriptor is still open, since closed descriptors can be reused by the operating system.

_Developers tightening resource-safety guarantees in Rust APIs can keep tabs on patterns like this through daily.dev._

### Why did Cloudflare's custom HTTP status codes break the http-types Rust crate?

The http-types StatusCode enum only modeled standard HTTP status codes as named variants, so constructing a response with one of Cloudflare's custom, non-standard status codes panicked because the enum had no way to represent an unrecognized value. The http crate's fix was to make StatusCode an opaque struct with associated constants plus a from_u16 constructor, letting any numeric code be represented even without a name.

_Anyone weighing enum versus opaque-struct trade-offs for Rust API evolution can find deep dives like this via daily.dev._

## Community discussion

Top comments from developers on daily.dev.

**@pdfopsdev** · 1 upvotes

> the non_exhaustive enum point is underrated. ran into exactly that with a status code enum at a previous job, adding a variant broke three downstream match statements because nobody'd left a wildcard arm.

**@ahmetozel** · 0 upvotes

> Ownership extending to lock guards is a useful API-design lens because releasing a resource at the wrong point is often more damaging than choosing the wrong container type. I would pay particular attention to whether a returned value implicitly keeps a lock held, especially when callers can retain it across unrelated work.
>
> The type can encode the lifetime correctly and the API can still be surprising operationally. Naming the returned guard clearly, documenting the blocking implications, and offering an explicit copy-out path help callers reason locally about both memory safety and...

## Similar posts on daily.dev

- [a grand vision for rust](https://daily.dev/posts/a-grand-vision-for-rust-fnxjbzooa) · Lobsters · 0 upvotes · 0 comments
- [Choosing Rust for All the Wrong Reasons \(Diary of a Mercenary\)](https://daily.dev/posts/choosing-rust-for-all-the-wrong-reasons-diary-of-a-mercenary--p8m8obyxf) · Medium · 3 upvotes · 0 comments
- [Beyond Memory Safety: What Makes Rust Different – Lessons from Autonomous Robotics](https://daily.dev/posts/beyond-memory-safety-what-makes-rust-different-lessons-from-autonomous-robotics-ivljxogzh) · InfoQ · 0 upvotes · 0 comments

---

Tags: [#architecture](https://daily.dev/tags/architecture), [#rust](https://daily.dev/tags/rust), [#type-systems](https://daily.dev/tags/type-systems)

[View this post on daily.dev](https://daily.dev/posts/practical-rust-api-design-qwmqrnfyi)

```json
{"@context":"https://schema.org","@graph":[{"@type":"Organization","@id":"https://daily.dev/#organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180},"sameAs":["https://twitter.com/dailydotdev","https://github.com/dailydotdev","https://www.linkedin.com/company/daily-dev-ltd"]},{"@type":"WebSite","@id":"https://daily.dev/#website","url":"https://daily.dev","name":"daily.dev","publisher":{"@id":"https://daily.dev/#organization"},"potentialAction":{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https://daily.dev/search?q={search_term_string}"},"query-input":"required name=search_term_string"}}]}
{"@context":"https://schema.org","@type":"TechArticle","headline":"Practical Rust API Design","url":"https://daily.dev/posts/practical-rust-api-design-qwmqrnfyi","mainEntityOfPage":{"@type":"WebPage","@id":"https://daily.dev/posts/practical-rust-api-design-qwmqrnfyi"},"datePublished":"2026-10-02T10:23:30.816Z","dateModified":"2026-10-02T10:23:54.387Z","description":"A deep dive into designing ergonomic, hard-to-misuse Rust APIs through local reasoning. Covers encoding relationships in function signatures (lifetimes,...","image":"https://media.daily.dev/image/upload/f_auto,q_auto/v1/posts/a417d294c8d54a8eb665a87a30c50a42?_a=AQAEuop","thumbnailUrl":"https://media.daily.dev/image/upload/f_auto,q_auto/v1/posts/a417d294c8d54a8eb665a87a30c50a42?_a=AQAEuop","isAccessibleForFree":true,"articleSection":"corrode","inLanguage":"en","publisher":{"@type":"Organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180}},"author":{"@type":"Organization","name":"corrode","logo":"https://media.daily.dev/image/upload/s--pI2ONj8J--/f_auto,q_auto/v1780213709/logos/corrode-blog?_a=BAMAMiWQ0","url":"https://daily.dev/sources/corrode-blog"},"commentCount":2,"discussionUrl":"https://daily.dev/posts/practical-rust-api-design-qwmqrnfyi","interactionStatistic":[{"@type":"InteractionCounter","interactionType":{"@type":"LikeAction"},"userInteractionCount":26},{"@type":"InteractionCounter","interactionType":{"@type":"CommentAction"},"userInteractionCount":2}],"keywords":"architecture,rust,type-systems","timeRequired":"PT16M"}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://daily.dev"},{"@type":"ListItem","position":2,"name":"corrode","item":"https://daily.dev/sources/corrode-blog"},{"@type":"ListItem","position":3,"name":"Practical Rust API Design"}]}
{"@context":"https://schema.org","@type":"WebPage","@id":"https://daily.dev/posts/practical-rust-api-design-qwmqrnfyi","comment":[{"@type":"Comment","text":"the non_exhaustive enum point is underrated. ran into exactly that with a status code enum at a previous job, adding a variant broke three downstream match statements because nobody’d left a wildcard arm.","datePublished":"2026-10-02T18:05:57.336Z","url":"https://daily.dev/posts/qWMqrnfYI#c-q1cdjAfEz","author":{"@type":"Person","name":"PDFops","url":"https://daily.dev/pdfopsdev","image":"https://media.daily.dev/image/upload/s---8isRBKc--/f_auto/v1782922291/avatars/avatar_orjMeK8QKaaVZwGq7ScPz?_a=BAMAMicg0"},"interactionStatistic":{"@type":"InteractionCounter","interactionType":{"@type":"LikeAction"},"userInteractionCount":1}},{"@type":"Comment","text":"Ownership extending to lock guards is a useful API-design lens because releasing a resource at the wrong point is often more damaging than choosing the wrong container type. I would pay particular attention to whether a returned value implicitly keeps a lock held, especially when callers can retain it across unrelated work.\nThe type can encode the lifetime correctly and the API can still be surprising operationally. Naming the returned guard clearly, documenting the blocking implications, and offering an explicit copy-out path help callers reason locally about both memory safety and contention. That is where ergonomic convenience should be weighed against an invisible cost.","datePublished":"2026-10-04T17:01:35.286Z","url":"https://daily.dev/posts/qWMqrnfYI#c-1Wn0Hg0Fk","author":{"@type":"Person","name":"Ahmet Özel","url":"https://daily.dev/ahmetozel","image":"https://avatars.githubusercontent.com/u/70992231?v=4"}}]}
{"@context":"https://schema.org","@type":"FAQPage","@id":"https://daily.dev/posts/practical-rust-api-design-qwmqrnfyi#faq","mainEntity":[{"@type":"Question","name":"What's the difference between an unsafe fn and an unsafe block in Rust?","acceptedAnswer":{"@type":"Answer","text":"An unsafe fn declares safety conditions that its caller must establish before calling it, shifting responsibility to the caller, while an unsafe block marks where the person writing the code asserts that the safety conditions of the unsafe operations inside have already been satisfied. Both should carry safety comments explaining why an operation is safe, not just that it is safe. daily.dev surfaces practical writing like this for developers refining how they reason about unsafe Rust boundaries."}},{"@type":"Question","name":"Why use BorrowedFd instead of RawFd in a Rust function signature?","acceptedAnswer":{"@type":"Answer","text":"BorrowedFd guarantees the file descriptor remains open for the duration of the borrow, letting the compiler check that the owning File outlives any use of the descriptor, which prevents time-of-check-to-time-of-use bugs. RawFd is just a bare integer alias for c_int with no guarantee the descriptor is still open, since closed descriptors can be reused by the operating system. Developers tightening resource-safety guarantees in Rust APIs can keep tabs on patterns like this through daily.dev."}},{"@type":"Question","name":"Why did Cloudflare's custom HTTP status codes break the http-types Rust crate?","acceptedAnswer":{"@type":"Answer","text":"The http-types StatusCode enum only modeled standard HTTP status codes as named variants, so constructing a response with one of Cloudflare's custom, non-standard status codes panicked because the enum had no way to represent an unrecognized value. The http crate's fix was to make StatusCode an opaque struct with associated constants plus a from_u16 constructor, letting any numeric code be represented even without a name. Anyone weighing enum versus opaque-struct trade-offs for Rust API evolution can find deep dives like this via daily.dev."}}]}
```

