#documentation #proc-macro #doc-comment #zero-cost

doc_for

📖 Get the documentation comment for structs, enums and unions, in a zero-cost fashion

5 releases

0.1.4 Mar 15, 2025
0.1.3 Feb 28, 2025
0.1.2 Feb 27, 2025
0.1.1 Feb 27, 2025
0.1.0 Feb 26, 2025

#155 in Procedural macros

Download history 407/week @ 2025-02-26 29/week @ 2025-03-05 138/week @ 2025-03-12 41/week @ 2025-03-19

615 downloads per month
Used in 2 crates

GPL-3.0-or-later

21KB
96 lines

doc_for

GitHub License Crates.io Version Crates.io Total Downloads docs.rs

[WARNING] This crate is still in development, and the API is subject to BREAKING CHANGES.

📖 Get the documentation comment for structs, enums and unions, in a zero-cost fashion.

🪄 Features

  • Zero-cost: All work is done at compile-time
  • Simple: Just annotate your struct with #[doc_impl] and use the doc_for! or doc! macro

🤔 Usage

Get the documentation comment for a type

First, bring doc_for and doc_impl into scope:

use doc_for::{doc_for, doc_impl};

Then, annotate your struct with #[doc_impl] attribute macro:

# use doc_for::{doc_for, doc_impl};
#
/// Some documentation
#[doc_impl]
struct MyStruct {
    field: i32,
}

Finally, use the doc_for! macro to get the documentation comment, which returns an Option<&'static str>:

# use doc_for::{doc_for, doc_impl};
#
# /// Some documentation
# #[doc_impl]
# struct MyStruct {
#     field: i32,
# }
assert_eq!(doc_for!(MyStruct).unwrap(), " Some documentation");

Note that the leading spaces are preserved. Multi-line comments are also supported:

# use doc_for::{doc_for, doc_impl};
#
/// Some documentation
/// that spans multiple lines
///
/// Additional information
#[doc_impl]
struct MyStruct {
    field: i32,
}
assert_eq!(doc_for!(MyStruct).unwrap(), r#" Some documentation
 that spans multiple lines

 Additional information"#);

If the type does not have a documentation comment, doc_for! will return None:

# use doc_for::{doc_for, doc_impl};
#
// No documentation comment here
#[doc_impl]
struct MyStruct {
    field: i32,
}
assert!(doc_for!(MyStruct).is_none());

Also works with tuple structs, enums and unions:

# use doc_for::{doc_for, doc_impl};
#
/// Tuple struct documentation
#[doc_impl]
struct MyTupleStruct(i32);
assert_eq!(doc_for!(MyTupleStruct).unwrap(), " Tuple struct documentation");

/// Enum documentation
#[doc_impl]
enum MyEnum {
    Variant,
}
assert_eq!(doc_for!(MyEnum).unwrap(), " Enum documentation");

/// Union documentation
#[doc_impl]
union MyUnion {
    field: i32,
}
assert_eq!(doc_for!(MyUnion).unwrap(), " Union documentation");

Get the documentation comment for fields and variants

Same as before, bring doc_impl and doc_for! into scope and annotate your struct with #[doc_impl] attribute macro:

use doc_for::{doc_for, doc_impl};

#[doc_impl]
struct MyStruct {
    /// Field documentation
    field: i32,
    not_documented: i32,
}

Then, use the doc_for! macro to get the documentation comment. If the field does not have a documentation comment, doc_for! will return None:

# use doc_for::{doc_for, doc_impl};
#
# #[doc_impl]
# struct MyStruct {
#     /// Field documentation
#     field: i32,
#     not_documented: i32,
# }
assert_eq!(doc_for!(MyStruct, field).unwrap(), " Field documentation");
assert!(doc_for!(MyStruct, not_documented).is_none());

If the field or variant does not exist, doc_for! will panic, thus failing the compilation:

# use doc_for::{doc_for, doc_impl};
#
# #[doc_impl]
# struct MyStruct {
#     /// Field documentation
#     field: i32,
#     not_documented: i32,
# }
// Won't compile due to `Field or variant does not exist`
assert_eq!(doc_for!(MyStruct, non_existent), None);

Similarly, it also works with union fields (not listed here), enum variants and tuple struct fields:

# use doc_for::{doc_for, doc_impl};
#
#[doc_impl]
enum MyEnum {
    /// Variant documentation
    Variant,
    NotDocumented,
}
assert_eq!(doc_for!(MyEnum, Variant).unwrap(), " Variant documentation");
assert!(doc_for!(MyEnum, NotDocumented).is_none());
// Won't compile due to `Field or variant does not exist`
// assert_eq!(doc_for!(MyEnum, NonExistent), None);

#[doc_impl]
struct MyTupleStruct(
    /// Tuple struct field documentation
    i32,
    i32,
);
assert_eq!(doc_for!(MyTupleStruct, 0).unwrap(), " Tuple struct field documentation");
assert!(doc_for!(MyTupleStruct, 1).is_none());
// Won't compile due to `Field or variant does not exist`
// assert_eq!(doc_for!(MyTupleStruct, 2), None);

Stripping the documentation comment

The strip attribute can be used to strip leading whitespace characters of the documentation comment. If all, all will be stripped; if n, at most n whitespace characters will be stripped. Default is 0.

use doc_for::{doc_for, doc_impl};

/// Some documentation
#[doc_impl(strip = 1)]
struct MyStruct {
    field: i32,
}
assert_eq!(doc_for!(MyStruct).unwrap(), "Some documentation");

///  Two leading spaces
#[doc_impl(strip = 1)]
struct TwoLeadingSpaces {
    field: i32,
}
assert_eq!(doc_for!(TwoLeadingSpaces).unwrap(), " Two leading spaces");

///          Too many spaces
#[doc_impl(strip = all)]
struct TooManySpaces {
    field: i32,
}
assert_eq!(doc_for!(TooManySpaces).unwrap(), "Too many spaces");

If you don't care about the Option

The doc! macro is basically doc_for! with unwrap:

use doc_for::{doc, doc_impl};

#[doc_impl]
struct MyStruct {
    /// Field documentation
    field: i32,
    not_documented: i32,
}

assert_eq!(doc!(MyStruct, field), " Field documentation");

...So it panics and fails the compilation if the requested type or field is not documented:

# use doc_for::{doc, doc_impl};
#
# #[doc_impl]
# struct MyStruct {
#    /// Field documentation
#    field: i32,
#    not_documented: i32,
# }
#
// Won't compile due to `The type is not documented`
println!("{}", doc!(MyStruct));
# use doc_for::{doc, doc_impl};
#
# #[doc_impl]
# struct MyStruct {
#    /// Field documentation
#    field: i32,
#    not_documented: i32,
# }
#
// Won't compile due to `The field is not documented`
println!("{}", doc!(MyStruct, not_documented));

Of course, trying to access a non-existent field or variant will also fail the compilation:

# use doc_for::{doc, doc_impl};
#
# #[doc_impl]
# struct MyStruct {
#    /// Field documentation
#    field: i32,
#    not_documented: i32,
# }
#
// Won't compile due to `Field or variant does not exist`
println!("{}", doc!(MyStruct, non_existent));

Get the documentation comment for an enum variant

This time, bring DocDyn and doc_impl into scope:

use doc_for::{DocDyn, doc_impl};

Then, annotate your enum with #[doc_impl(doc_for = false, doc_dyn = true)]:

# use doc_for::{DocDyn, doc_impl};
#
#[doc_impl(doc_for = false, doc_dyn = true)]
enum MyEnum {
    /// Variant documentation
    Variant,
    NotDocumented,
}

Finally, call the doc_dyn method on the enum variant:

# use doc_for::{DocDyn, doc_impl};
#
# #[doc_impl(doc_for = false, doc_dyn = true)]
# enum MyEnum {
#     /// Variant documentation
#     Variant,
#     NotDocumented,
# }
assert_eq!(MyEnum::Variant.doc_dyn().unwrap(), " Variant documentation");
assert!(MyEnum::NotDocumented.doc_dyn().is_none());

Note that this method is not zero-cost, as it matches the enum variant at runtime.

To use both doc_for! and doc_dyn on the same enum, annotate it with #[doc_impl(doc_dyn = true)]. You can include doc_for = true if you want, but since it's the default, it's not necessary.

# use doc_for::{DocDyn, doc_for, doc_impl};
#
#[doc_impl(doc_dyn = true, strip = 1)]
enum MyEnum {
    /// Variant documentation
    Variant,
    NotDocumented,
}

assert_eq!(doc_for!(MyEnum, Variant).unwrap(), "Variant documentation");
assert_eq!(MyEnum::Variant.doc_dyn().unwrap(), "Variant documentation");

Automatically generate attribute macros with documentation as parameters

Consider the following scenario:

use thiserror::Error;

#[derive(Debug, Error)]
enum MyError {
    /// Error1 message
    #[error("Error1 message")]
    Error1,
    /// Error2 message
    #[error("Error2 message")]
    Error2,
}

Which seems quite repetitive. Luckily, doc_impl provides a way to automatically generate those repetitive attribute macros. Simply append gen_attr = "your_attr({doc})", and respective documentation will take the place of {doc}:

use doc_for::doc_impl;
use thiserror::Error;

/// Some documentation
#[doc_impl(strip = 1, doc_for = false, gen_attr = "error({doc})")]
#[derive(Debug, Error)]
enum MyError {
    /// Error1 message
    Error1,
    /// Error2 message
    Error2,
}

assert_eq!(format!("{}", MyError::Error1), "Error1 message");
assert_eq!(format!("{}", MyError::Error2), "Error2 message");

Also works on struct fields, where you might want to generate #[serde(rename = "...")] attributes:

use doc_for::doc_impl;
use serde::Deserialize;

/// Some documentation
#[doc_impl(strip = 1, doc_for = false, gen_attr = "serde(rename = {doc})")]
#[derive(Deserialize)]
struct MyStruct {
    /// field1_rename
    // No need for #[serde(rename = "field1_rename")]
    field1: i32,
    /// field2_rename
    // No need for #[serde(rename = "field2_rename")]
    field2: i32,
}

let json = r#"{"field1_rename": 1, "field2_rename": 2}"#;
let my_struct: MyStruct = serde_json::from_str(json).unwrap();
assert_eq!(my_struct.field1, 1);
assert_eq!(my_struct.field2, 2);

Do note that:

  • doc_impl annotation must be placed BEFORE attribute macros that introduced the target attribute.
  • gen_attr can be used multiple times.

The derive alternative

If you prefer to use derive, you can use DocFor and DocDyn to replace doc_for and doc_dyn respectively:

use doc_for::{DocDyn, DocFor, doc_for};

#[derive(DocFor, DocDyn)]
/// Some documentation
enum MyEnum {
    /// Variant documentation
    Variant,
    NotDocumented,
}

assert_eq!(doc_for!(MyEnum).unwrap(), " Some documentation");
assert_eq!(doc_for!(MyEnum, Variant).unwrap(), " Variant documentation");
assert_eq!(MyEnum::Variant.doc_dyn().unwrap(), " Variant documentation");

However, you won't be able to configure the strip and gen_attr attribute in this case.

⚙️ Implementation

DocFor and doc_for!

The doc_for crate provides a DocFor trait and a doc_for! macro:

  • The DocFor trait requires an associated constant DOC to be implemented for the type
  • Deriving the DocFor trait sets the DOC constant as the documentation comment of the type, and generates a const fn doc_for_field(name) -> Option<&'static str> function
    • Currently Rust doesn't support constant functions in traits, so the doc_for_field function is implemented directly on the annotated type
    • If the annotated type is a struct, union or enum, the name parameter accepts a &'static str
    • If the annotated type is a tuple struct, the name parameter accepts an usize
  • If given a type, the doc_for! macro retrieves the value of this constant; If given a type and a field name, the doc_for! macro calls the doc_for_field function with the given field name

Using these APIs is zero-cost, as all the work is done at compile-time:

  • When compiled, types that derive DocFor will have their documentation comments inlined as associated constants or in constant functions
  • Calls to doc_for! will be replaced with the value of the associated constant or the result of the constant function

DocDyn and doc_dyn

The doc_for crate also provides a DocDyn trait and a doc_dyn method:

  • The DocDyn trait requires a doc_dyn method to be implemented for the type, which returns an Option<&'static str>
  • Deriving the DocDyn trait generates a doc_dyn method, which returns the documentation comment that matches the variant of the enum

This method is not zero-cost, as it matches the enum variant at runtime.

doc_impl

The doc_impl attribute macro is used to derive the DocFor and DocDyn traits for a type, along with configuring the strip attribute. gen_attr attribute, when set, prepends the specified attribute macros to fields or variants.

✅ TODO

  • Strip each line of the documentation comment, via a strip attribute
  • Better error reporting and handling
  • Access module documentation (e.g. doc_for!(my_module))
  • Access trait documentation (e.g. doc_for!(MyTrait))
  • Access sub-item documentation
    • Access field documentation (e.g. doc_for!(MyStruct, field) or doc_for!(MyUnion, field))
    • Access tuple struct field documentation (e.g. doc_for!(MyTupleStruct, 0))
    • Access enum variant documentation (statically) (e.g. doc_for!(MyEnum, Variant))
    • Access enum variant documentation (dynamically) (e.g. doc_for!(my_enum_variant))
    • Access method documentation (e.g. doc_for!(MyStruct, method))
    • Access associated constant documentation (e.g. doc_for!(MyStruct, CONSTANT))
    • Access associated type documentation (e.g. doc_for!(MyStruct, Type))

Dependencies

~205–640KB
~15K SLoC