Crate doc_for

Source
Expand description

§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:

/// 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>:

assert_eq!(doc_for!(MyStruct).unwrap(), " Some documentation");

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

/// 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:

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

Also works with tuple structs, enums and unions:

/// 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:

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:

// 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:

#[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:

// Won't compile due to `The type is not documented`
println!("{}", doc!(MyStruct));
// 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:

// 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)]:

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

Finally, call the doc_dyn method on the enum variant:

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.

#[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))

Macros§

doc
Get the documentation comment for a type or its fields. Basically doc_for! with unwrap.
doc_for
Get the documentation comment for a type or its fields, returning None if not documented.

Traits§

DocDyn
Trait for enums that allows getting the documentation comment for the variant.
DocFor
Trait for types that allows getting the documentation comment for the type.

Attribute Macros§

doc_impl
Derives the DocFor trait and doc_for_field method for a type.

Derive Macros§

DocDyn
Derives the DocDyn trait for an enum type, providing doc_dyn method. Does not strip leading whitespaces.
DocFor
Derives the DocFor trait and doc_for_field method for a type. Does not strip leading whitespaces.