Skip to content

← Errors Are Values step 24 of 24

Medium Framework

Documenting failure: # Errors and # Panics

Take the AppError / parse_record code you already wrote and document its failure modes. The behaviour must not change — every test case is the same as before, so you cannot pass this by editing the logic.

pub fn parse_record(line: String) -> Result<(i32, f64), AppError>
pub fn record_ratio(line: String) -> f64

record_ratio is new: it parses a record and returns value / count. It panics on a malformed record and on a zero count — deliberately, because it is a convenience wrapper for input a caller has already validated.

The gate

#![deny(
    clippy::missing_errors_doc,
    clippy::missing_panics_doc,
    clippy::missing_safety_doc
)]

and the starter, which is behaviourally perfect, does not build:

error: docs for function returning `Result` missing `# Errors` section
   |
   | pub fn parse_record(line: String) -> Result<(i32, f64), AppError> {
   |
error: docs for function which may panic missing `# Panics` section
   |
   | pub fn record_ratio(line: String) -> f64 {

Two sections, written as ordinary Markdown headings inside a /// doc comment:

/// One-line summary.
///
/// # Errors
///
/// Returns [`AppError::Empty`] if the line is blank once trimmed, …
pub fn parse_record(line: String) -> Result<(i32, f64), AppError> { … }

missing_panics_doc detects panic!, unwrap, expect and assert! in the body. record_ratio has two of those, so it needs a # Panics section saying what triggers them.

Why this is a real gate and not bureaucracy

Most professional Rust codebases turn these two lints on, and the reason is not tidiness. A Result in a signature says that a function can fail; nothing in the type says when, or which variant to expect for which input. That information exists only in the author’s head at the moment they write the function, and the # Errors section is the only place it can be written down before it evaporates.

There is a second, sneakier benefit. Writing the section forces you to enumerate your own failure modes in prose — and an error enum that has grown incoherent is very obvious when you try. If your # Errors section reads “returns Error::Other if something goes wrong”, you have just discovered that your error type is not carrying its weight.

The same for # Panics. Every panic in a library is a contract term: “if you call me like this, I will take your process down”. Undocumented, it is a landmine. Documented, it is an API.

What the lint can and cannot check

Be clear-eyed about this: the lint only checks that the section exists. It does not read it. # Errors\n\nSometimes. passes. That is why the behavioural test cases are still here, and why “keep the docs accurate” remains a human job.

There is also a trap that costs people twenty minutes: both lints only apply to pub items. Remove pub from parse_record and every diagnostic vanishes — not because the docs got better, but because clippy stopped caring. If you are turning these on in a real crate and see zero warnings, check your visibility before you congratulate yourself.

Conventions worth copying

  • The first paragraph is the summary and should be one sentence. too_long_first_doc_paragraph (pedantic) exists because a wall of text there ruins the module index page, where only that first paragraph is shown.
  • Link to types with intra-doc links: [`AppError::Empty`] renders as a real hyperlink in rustdoc and breaks the build if the path is wrong.
  • # Errors, # Panics and # Safety are the conventional headings, in that order, after the summary and any examples. # Safety is for unsafe fnmissing_safety_doc is on by default, and unnecessary_safety_doc fires if you add one to a safe function.
  • Document the enum variants too. missing_docs_in_private_items is a restriction lint and off here, but a # Errors section that names variants is much more useful when clicking through to the variant tells you more.

Notes

  • empty_docs catches a /// with nothing after it; doc_lazy_continuation catches a list item whose continuation line is not indented, which silently ends the list in the rendered output.
  • suspicious_doc_comments catches //! used where /// was meant — an inner doc comment placed just before an item documents the enclosing module instead, and the resulting rustdoc is confusing rather than broken.
  • record_ratio keeps its #[must_use]: it is a pure function returning a number, so calling it and dropping the result is meaningless.

Remember the grade is compile + tests + clippy -D warnings.