Crate fieldwork

Source
Expand description

§⛏️ fieldwork - field accessor generation

fieldwork is a procedural macro crate designed to automate the generation of field accessor methods for structs. By leveraging Rust’s powerful macro system, fieldwork reduces boilerplate code, enhances code readability, and ensures consistency. Just as importantly, fieldwork intends to be fully customizable and expressive for common access patterns.

Manually writing getters and setters for struct fields is repetitive, and adds to maintenance burden. fieldwork addresses this by providing a procedural macro that automatically generates these methods based on your struct definitions. The intent of this crate, and distinguishing feature, is to be as customizable and expressive as writing your own getters and setters. The crate succeeds if you are able to emit exactly the code that you would have manually written, but far more concisely.

Although this crate is fully configurable and these docs carefully describe the many configuration settings, wherever possible, fieldwork tries to express common patterns as the default.

§Performance

The compile time cost of using a proc macro crate is always worth considering. All efforts have been made to keep this crate as lightweight as possible and featureful enough to be worth the tradeoff.

§Testing

ci codecov

This crate has a full suite of macro-expansion tests in tests/expand. These tests are also used for test coverage.

§Configuration

fieldwork supports four layers of configuration, from broadest to most specific: struct configuration, struct method configuration, field configuration and field method configuration. The most specific configuration always has precedence.

Methods: get, set, get_mut, with, without, take

Struct configuration: vis, where_clause, option_borrow_inner, deref, option_set_some, into, rename_predicates

Struct method configuration: vis, doc_template, template, chain, option_borrow_inner, deref, option_set_some, into, copy

Field configuration: skip, name, argument, vis, deref, option_set_some, into, option_borrow_inner

Field method configuration: name, argument, doc, chain, copy, skip, deref, option_set_some, into, option_borrow_inner

How fieldwork selects which methods to generate for which fields

§Example to get a sense of the library

use std::path::PathBuf;

#[derive(fieldwork::Fieldwork, Default)]
#[fieldwork(get, set, with, without, get_mut, take, into, rename_predicates)]
struct ServerConfig {
    /// server hostname
    host: String,

    /// server port
    #[field(into = false)]
    port: u16,

    /// path to SSL certificate file
    ssl_cert: Option<PathBuf>,

    /// path to log directory  
    log_dir: Option<PathBuf>,

    /// whether TLS is required
    tls_required: bool,

    /// whether verbose logging is enabled
    verbose: bool,

    #[field = false]
    _runtime_data: (),
}

// Usage examples:
let mut config = ServerConfig::default()
    .with_host("LocalHost") // accepts &str via Into<String>
    .with_port(8080)
    .with_log_dir("/var/log") // accepts &str, wraps in Some() automatically
    .with_tls_required() // sets to true
    .without_verbose(); // sets to false

config.host_mut().make_ascii_lowercase();

// Getters use idiomatic naming
assert_eq!(config.host(), "localhost");
assert_eq!(config.port(), 8080);
assert_eq!(config.log_dir().unwrap(), PathBuf::from("/var/log"));
assert!(config.is_tls_required()); // boolean getters use is_ prefix because of `rename_predicates`
assert!(!config.is_verbose());

// Chainable setters return &mut Self
config
    .set_ssl_cert(PathBuf::from("/etc/ssl/cert.pem"))
    .set_port(9090)
    .set_verbose(true);

let cert = config.take_ssl_cert();
assert_eq!(cert, Some(PathBuf::from("/etc/ssl/cert.pem")));

// Without methods provide convenient clearing
config = config.without_log_dir(); // Sets log_dir to None
assert!(config.log_dir().is_none());




§General notes and configuration

§#[field] and #[fieldwork] attributes are used to configure fieldwork

Fieldwork can be configured on structs and fields through #[field] and #[fieldwork], interchangeably. These docs generally use #[fieldwork] for the structs and #[field] for the indiviual fields.

§Fieldwork has four configuration levels that cascade

Configuration at each field method inherits from the field’s configuration, the struct configuration for that method, and the struct’s top level configuration. The most specific configuration always takes precedence. The intent of this approach is to avoid duplication and do what you intend.

§Boolean handling

#[field(option_borrow_inner = true)] is the same as #[field(option_borrow_inner)] and this is the case anywhere booleans are accepted.

§Type quoting

Some types will need to be quoted if they contain lifetimes, brackets, or generics. Simple path types like std::sync::Arc do not need to be quoted.

§Common dereference types are detected by default

Cwned types that Deref to commonly used borrowed types will automatically get detected and dereferenced as such. So for example, a field that contains a String will return &str from get or &mut str from get_mut by default. This behavior can be opted out of, at any configuration level.

Owned TypeBorrowed TypeDerefMut
String&stryes
OsString&OsStryes
Vec<T>&[T]yes
Box<T>&Tyes
Arc<T>&Tno
Rc<T>&Tno
PathBuf&Pathyes
Cow<T>&Tno
[T; N]&[T]yes

§Common copy types are detected by default

The current list of types that are detected are: bool, char, numeric primitives, and immutable references (&T). Almost always, if a getter returns a &bool, the caller will want to dereference that immediately, so by default get returns those types by copy. This behavior can be opted out of at the struct method level with #[fieldwork(get(copy = false))] or at the field method level with the same invocation.

§Options are returned as_ref or as_deref by default

By default, fieldwork detects Options and calls as_deref or as_ref on them, so instead of getting &Option<String>, you get Option<&str> by default. It is possible to opt out of the option detection behavior and the deref detection behavior distinctly, so you can have it return Option<&String> or &Option<String>, at any configuration level.

§Option setters can automatically wrap values in Some

When option_set_some is enabled, set and with methods for Option<T> fields will accept T as input and automatically wrap it in Some(T). This is useful for builder patterns and situations where None represents an unset default value that is only ever replaced with populated values. Instead of calling user.set_name(Some("Alice".to_string())), you can simply call user.set_name("Alice".to_string()). This feature can be enabled at any configuration level and only affects setter methods - getters remain unchanged.

§Setters can accept impl Into for ergonomic APIs

When into is enabled, set and with methods will accept impl Into<T> instead of T as their parameter. This allows callers to pass any type that can be converted into the field type, making APIs more ergonomic. For example, a String field can accept &str, String, Cow<str>, or any other type implementing Into<String>. This feature works seamlessly with option_set_some - when both are enabled, the setter accepts impl Into<T> and wraps the result in Some(). This feature can be enabled at any configuration level and only affects setter methods.

§Tuple struct support

Fieldwork supports both named structs and tuple structs. For tuple structs, you must provide a name attribute for each field you want to generate methods for. Fields without a name attribute are ignored.

#[derive(fieldwork::Fieldwork)]
#[fieldwork(get, set, with, without, get_mut)]
struct Color(
    #[field = "red"] u8,
    #[field = "green"] u8,
    #[field = "blue"] u8,
);

// Usage
let color = Color(255, 128, 0)
    .with_red(200)
    .with_blue(100);

assert_eq!(color.red(), 200);
assert_eq!(color.green(), 128);
assert_eq!(color.blue(), 100);




§Methods

Fieldwork supports five distinct methods: get, set, get_mut, with, and without.

§get

Borrows the field. This can also be used to return a copy of the field using the #[field(get(copy))] annotation on a field, or when common copy types are detected (see above).

§Example:
#[derive(fieldwork::Fieldwork)]
#[fieldwork(get)]
struct User {
    /// whether this user is an admin
    admin: bool,

    /// the user's name
    name: String,

    /// the user's age, if set
    age: Option<u8>,

    /// favorite color, if set
    favorite_color: Option<String>
}

generates

// GENERATED
impl User {
    ///Returns a copy of whether this user is an admin
    pub fn admin(&self) -> bool {
        self.admin
    }
    ///Borrows the user's name
    pub fn name(&self) -> &str {
        &*self.name
    }
    ///Returns a copy of the user's age, if set
    pub fn age(&self) -> Option<u8> {
        self.age
    }
    ///Borrows favorite color, if set
    pub fn favorite_color(&self) -> Option<&str> {
        self.favorite_color.as_deref()
    }
}

§set

By default, set returns &mut self for chainable setters. If you would prefer to return (), use #[fieldwork(set(chain = false))] on the struct or an individual field.

§Example
#[derive(fieldwork::Fieldwork)]
#[fieldwork(set)]
struct User {
    /// whether this user is an admin
    admin: bool,

    /// the user's name
    name: String
}

generates:

// GENERATED
impl User {
    ///Sets whether this user is an admin, returning `&mut Self` for chaining
    pub fn set_admin(&mut self, admin: bool) -> &mut Self {
        self.admin = admin;
        self
    }
    ///Sets the user's name, returning `&mut Self` for chaining
    pub fn set_name(&mut self, name: String) -> &mut Self {
        self.name = name;
        self
    }
}

§get_mut

§Example
#[derive(fieldwork::Fieldwork)]
#[fieldwork(get_mut)]
struct User {
    /// whether this user is an admin
    admin: bool,

    /// the user's name
    name: String,

    /// the user's age, if set
    age: Option<u8>,

    /// favorite color, if set
    favorite_color: Option<String>
}

generates the following impl block

// GENERATED
impl User {
    ///Mutably borrow whether this user is an admin
    pub fn admin_mut(&mut self) -> &mut bool {
        &mut self.admin
    }
    ///Mutably borrow the user's name
    pub fn name_mut(&mut self) -> &mut str {
        &mut *self.name
    }
    ///Mutably borrow the user's age, if set
    pub fn age_mut(&mut self) -> Option<&mut u8> {
        self.age.as_mut()
    }
    ///Mutably borrow favorite color, if set
    pub fn favorite_color_mut(&mut self) -> Option<&mut str> {
        self.favorite_color.as_deref_mut()
    }
}

§with

The with method provides a chainable owned setter, for situations that require returning the struct after modification.

§Example
#[derive(fieldwork::Fieldwork)]
#[fieldwork(with)]
struct User {
    /// whether this user is an admin
    admin: bool,

    /// the user's name
    name: String,
}
// GENERATED
impl User {
    ///Owned chainable setter for whether this user is an admin, returning `Self`
    #[must_use]
    pub fn with_admin(mut self, admin: bool) -> Self {
        self.admin = admin;
        self
    }
    ///Owned chainable setter for the user's name, returning `Self`
    #[must_use]
    pub fn with_name(mut self, name: String) -> Self {
        self.name = name;
        self
    }
}

§without

The without method provides “negative” operations to complement with. When without is enabled, it changes how with works: instead of accepting the field’s type directly, with becomes the “positive” operation (true for bools, Some(value) for Options) and without becomes the “negative” operation (false for bools, None for Options).

§How with and without work together
// With `without` - the `with` methods change behavior
#[derive(fieldwork::Fieldwork)]
#[fieldwork(with, without)]
struct Config {
    debug: bool,
    name: Option<String>,
}
// GENERATED
impl Config {
    #[must_use]
    pub fn with_debug(mut self) -> Self {
        self.debug = true;
        self
    }
    #[must_use]
    pub fn without_debug(mut self) -> Self {
        self.debug = false;
        self
    }
    #[must_use]
    pub fn with_name(mut self, name: String) -> Self {
        self.name = Some(name);
        self
    }
    #[must_use]
    pub fn without_name(mut self) -> Self {
        self.name = None;
        self
    }
}

§take

Take is only ever generated for Option fields. It calls Option::take on the field, returning the contained value and leaving None in its place.

// With `without` - the `with` methods change behavior
#[derive(fieldwork::Fieldwork)]
#[fieldwork(take)]
struct Config {
    name: Option<String>,
}
// GENERATED
impl Config {
    pub fn take_name(&mut self) -> Option<String> {
        self.name.take()
    }
}
§Field type behavior
  • Option<T> fields: with_field(value: T) sets to Some(value), without_field() sets to None
  • bool fields: with_field() sets to true, without_field() sets to false
  • Other types: without methods are not generated, with methods work normally

The without behavior can be disabled per-field with #[field(without = false)], and the automatic Option<T> wrapping can be disabled with #[field(option_set_some = false)].




§Struct Configuration

vis

Sets the visibility for all generated functions, unless otherwise overridden. `#[fieldwork(vis = "pub")]` is the default. For `pub(crate)`, use `#[fieldwork(vis = "pub(crate)")]`. To set private visibility, use `#[fieldwork(vis = "")]`.

where_clause

This option allows you to specify a where clause for the implementation block, such as:

#[derive(fieldwork::Fieldwork, Clone)]
#[fieldwork(get, set, get_mut, with, where_clause = "PocketContents: Precious")]
struct Hobbit<PocketContents> {
    /// what the hobbit has in his pocket
    pocket_contents: PocketContents,
}
// GENERATED
impl<PocketContents> Hobbit<PocketContents>
where
    PocketContents: Precious,
{
    ///Borrows what the hobbit has in his pocket
    pub fn pocket_contents(&self) -> &PocketContents {
        &self.pocket_contents
    }
    ///Mutably borrow what the hobbit has in his pocket
    pub fn pocket_contents_mut(&mut self) -> &mut PocketContents {
        &mut self.pocket_contents
    }
    ///Sets what the hobbit has in his pocket, returning `&mut Self` for chaining
    pub fn set_pocket_contents(&mut self, pocket_contents: PocketContents) -> &mut Self {
        self.pocket_contents = pocket_contents;
        self
    }
    ///Owned chainable setter for what the hobbit has in his pocket, returning `Self`
    #[must_use]
    pub fn with_pocket_contents(mut self, pocket_contents: PocketContents) -> Self {
        self.pocket_contents = pocket_contents;
        self
    }
}

option_borrow_inner

Opt out of Option detection with option_borrow_inner = false. Instead of get returning Option<&T> and get_mut returning Option<&mut T>, get returns &Option<T> and get_mut returns &mut Option<T>. Default behavior is for Option detection to be enabled at the struct level.

#[derive(fieldwork::Fieldwork, Clone)]
#[fieldwork(get, get_mut, option_borrow_inner = false)]
struct User {
    // the user's name
    name: Option<String>
}
// GENERATED
impl User {
    pub fn name(&self) -> &Option<String> {
        &self.name
    }
    pub fn name_mut(&mut self) -> &mut Option<String> {
        &mut self.name
    }
}

deref

Opt out of auto-deref at the struct level with deref = false. See the Deref section for more information.

#[derive(fieldwork::Fieldwork, Clone)]
#[fieldwork(get, get_mut, deref = false)]
struct User {
    // the user's name
    name: String
}
// GENERATED
impl User {
    pub fn name(&self) -> &String {
        &self.name
    }
    pub fn name_mut(&mut self) -> &mut String {
        &mut self.name
    }
}

option_set_some

Enable automatic wrapping of setter values in Some() for Option<T> fields at the struct level with option_set_some or option_set_some = true. When enabled, set and with methods for Option fields will accept the inner type T instead of Option<T> and automatically wrap the value. This is particularly useful for builder patterns where None represents an unset default value.

#[derive(fieldwork::Fieldwork)]
#[fieldwork(set, with, option_set_some)]
struct User {
    /// the user's nickname, if provided
    nickname: Option<String>,
}
// GENERATED
impl User {
    ///Sets the user's nickname, if provided, returning `&mut Self` for chaining
    pub fn set_nickname(&mut self, nickname: String) -> &mut Self {
        self.nickname = Some(nickname);
        self
    }
    ///Owned chainable setter for the user's nickname, if provided, returning `Self`
    #[must_use]
    pub fn with_nickname(mut self, nickname: String) -> Self {
        self.nickname = Some(nickname);
        self
    }
}

into

Enable impl Into<T> parameters for setter methods at the struct level with into or into = true. When enabled, set and with methods will accept impl Into<T> instead of T, allowing callers to pass any type that can be converted into the field type.

#[derive(fieldwork::Fieldwork)]
#[fieldwork(set, with, into)]
struct User {
    /// the user's name
    name: String,
    
    /// the user's home directory
    home_dir: PathBuf,
}
// GENERATED
impl User {
    ///Sets the user's name, returning `&mut Self` for chaining
    pub fn set_name(&mut self, name: impl Into<String>) -> &mut Self {
        self.name = name.into();
        self
    }
    ///Owned chainable setter for the user's name, returning `Self`
    #[must_use]
    pub fn with_name(mut self, name: impl Into<String>) -> Self {
        self.name = name.into();
        self
    }
    ///Sets the user's home directory, returning `&mut Self` for chaining
    pub fn set_home_dir(&mut self, home_dir: impl Into<PathBuf>) -> &mut Self {
        self.home_dir = home_dir.into();
        self
    }
    ///Owned chainable setter for the user's home directory, returning `Self`
    #[must_use]
    pub fn with_home_dir(mut self, home_dir: impl Into<PathBuf>) -> Self {
        self.home_dir = home_dir.into();
        self
    }
}

rename_predicates

Rename all bool-returning methods to is_{} at the struct level with rename_predicates or rename_predicates = true.

#[derive(fieldwork::Fieldwork)]
#[fieldwork(get, get_mut, rename_predicates)]
struct User {
    admin: bool
}
// GENERATED
impl User {
    pub fn is_admin(&self) -> bool {
        self.admin
    }
    pub fn admin_mut(&mut self) -> &mut bool {
        &mut self.admin
    }
}




§Struct Method Configuration

vis

Override the struct-level definition for a specific method. #[vis = "pub(crate)", get(vis = "pub"), set, get_mut] uses pub(crate) for all methods other than get, which uses “pub”.

doc_template

Override the default documentation template for the specific method. Let’s say we want our documentation to say “assigns” instead of “sets”:

#[derive(fieldwork::Fieldwork)]
#[fieldwork(set(doc_template = "Assigns {}"))]
struct User {
    /// whether this user is an admin
    admin: bool,

    /// the user's name
    name: String,
}
// GENERATED
impl User {
    ///Assigns whether this user is an admin
    pub fn set_admin(&mut self, admin: bool) -> &mut Self {
        self.admin = admin;
        self
    }
    ///Assigns the user's name
    pub fn set_name(&mut self, name: String) -> &mut Self {
        self.name = name;
        self
    }
}

template

Override the method naming for all generated functions of this type. Let’s say we want our set signature to be assign_admin instead of set_admin and assign_name:

#[derive(fieldwork::Fieldwork)]
#[fieldwork(set(template = "assign_{}"))]
struct User {
    /// whether this user is an admin
    admin: bool,

    /// the user's name
    name: String,
}
// GENERATED
impl User {
    ///Sets whether this user is an admin, returning `&mut Self` for chaining
    pub fn assign_admin(&mut self, admin: bool) -> &mut Self {
        self.admin = admin;
        self
    }
    ///Sets the user's name, returning `&mut Self` for chaining
    pub fn assign_name(&mut self, name: String) -> &mut Self {
        self.name = name;
        self
    }
}

chain (set only)

As discussed in the Set section above, set returns &mut Self by default. To disable this, specify chain = false:

#[derive(fieldwork::Fieldwork)]
#[fieldwork(set(chain = false))]
struct User {
    /// whether this user is an admin
    admin: bool,

    /// the user's name
    name: String,
}
// GENERATED
impl User {
    ///Sets whether this user is an admin
    pub fn set_admin(&mut self, admin: bool) {
        self.admin = admin;
    }
    ///Sets the user's name
    pub fn set_name(&mut self, name: String) {
        self.name = name;
    }
}

option_borrow_inner

Opt out of Option detection with option_borrow_inner = false, or if it has been opted out at the struct level, opt back in with option_borrow_inner or option_borrow_inner = true for a single method, as in get(option_borrow_inner) or get_mut(option_borrow_inner = true). See option_borrow_inner above for more information.

deref

Opt out of auto-deref at the struct method level with deref = false. See the Deref section for more information.

#[derive(fieldwork::Fieldwork, Clone)]
#[fieldwork(get, get_mut(deref = false))]
struct User {
    // the user's name
    name: String
}
// GENERATED
impl User {
    pub fn name(&self) -> &str {
        &*self.name
    }
    pub fn name_mut(&mut self) -> &mut String {
        &mut self.name
    }
}

option_set_some (set and with only)

Enable automatic wrapping of setter values in Some() for Option<T> fields for a specific method. This can be used to enable the feature for just set or just with, or to override the struct-level setting.

#[derive(fieldwork::Fieldwork)]
#[fieldwork(set(option_set_some), with)]
struct User {
    /// the user's nickname, if provided
    nickname: Option<String>,
}
// GENERATED
impl User {
    ///Sets the user's nickname, if provided, returning `&mut Self` for chaining
    pub fn set_nickname(&mut self, nickname: String) -> &mut Self {
        self.nickname = Some(nickname);
        self
    }
    ///Owned chainable setter for the user's nickname, if provided, returning `Self`
    #[must_use]
    pub fn with_nickname(mut self, nickname: Option<String>) -> Self {
        self.nickname = nickname;
        self
    }
}

into (set and with only)

Enable impl Into<T> parameters for setter methods for a specific method. This can be used to enable the feature for just set or just with, or to override the struct-level setting.

#[derive(fieldwork::Fieldwork)]
#[fieldwork(set(into), with)]
struct User {
    /// the user's name
    name: String,
}
// GENERATED
impl User {
    ///Sets the user's name, returning `&mut Self` for chaining
    pub fn set_name(&mut self, name: impl Into<String>) -> &mut Self {
        self.name = name.into();
        self
    }
    ///Owned chainable setter for the user's name, returning `Self`
    #[must_use]
    pub fn with_name(mut self, name: String) -> Self {
        self.name = name;
        self
    }
}

copy (get only)

By default, common Copy types such as bool will be returned by copy instead of by reference. To opt out of this behavior for a whole struct, use #[fieldwork(get(copy = false))].

#[derive(fieldwork::Fieldwork)]
#[fieldwork(get(copy = false))]
struct Collection {
    /// length
    len: usize,

    /// enabled
    enabled: bool,
}
// GENERATED
impl Collection {
    ///Borrows length
    pub fn len(&self) -> &usize {
        &self.len
    }
    ///Borrows enabled
    pub fn enabled(&self) -> &bool {
        &self.enabled
    }
}




§Field Configuration

skip

Omit this field from all generated functions. As a shorthand, you can also use #[field = false].

#[derive(fieldwork::Fieldwork)]
#[fieldwork(get, set)]
struct User {
    /// whether this user is an admin
    admin: bool,

    /// the user's name
    name: String,

    #[field(skip)]
    private: (),

    #[field = false]
    other_private: ()
}
// GENERATED
impl User {
    ///Returns a copy of whether this user is an admin
    pub fn admin(&self) -> bool {
        self.admin
    }
    ///Sets whether this user is an admin, returning `&mut Self` for chaining
    pub fn set_admin(&mut self, admin: bool) -> &mut Self {
        self.admin = admin;
        self
    }
    ///Borrows the user's name
    pub fn name(&self) -> &str {
        &*self.name
    }
    ///Sets the user's name, returning `&mut Self` for chaining
    pub fn set_name(&mut self, name: String) -> &mut Self {
        self.name = name;
        self
    }
}

name

Change the name of this field for all generated methods. There are two ways to accomplish this. If you don’t need to specify any other configuration on the field, you can use #[field = "new_name"], or use #[field(name = new_name)]

#[derive(fieldwork::Fieldwork)]
#[fieldwork(get, set)]
struct User {
    #[field(name = admin)]
    /// whether this user is an admin
    superadmin: bool,

    #[field = "full_name"]
    name: String,
}
// GENERATED
impl User {
    ///Returns a copy of whether this user is an admin
    pub fn admin(&self) -> bool {
        self.superadmin
    }
    ///Sets whether this user is an admin, returning `&mut Self` for chaining
    pub fn set_admin(&mut self, admin: bool) -> &mut Self {
        self.superadmin = admin;
        self
    }
    pub fn full_name(&self) -> &str {
        &*self.name
    }
    pub fn set_full_name(&mut self, full_name: String) -> &mut Self {
        self.name = full_name;
        self
    }
}

argument

Change the name of the argument for with and set. This is occasionally important for rustdocs and lsp.

#[derive(fieldwork::Fieldwork)]
#[fieldwork(with, set)]
struct User {
    #[field(argument = is_admin)]
    /// whether this user is an admin
    admin: bool,
}
// GENERATED
impl User {
    ///Sets whether this user is an admin, returning `&mut Self` for chaining
    pub fn set_admin(&mut self, is_admin: bool) -> &mut Self {
        self.admin = is_admin;
        self
    }
    ///Owned chainable setter for whether this user is an admin, returning `Self`
    #[must_use]
    pub fn with_admin(mut self, is_admin: bool) -> Self {
        self.admin = is_admin;
        self
    }
}

vis

Change the visibility for all generated methods for a specific field

#[derive(fieldwork::Fieldwork)]
#[fieldwork(get, set)]
struct User {
    /// whether this user is an admin
    admin: bool,

    #[field(vis = "pub(crate)")]
    /// the user's name
    name: String,
}
// GENERATED
impl User {
    ///Returns a copy of whether this user is an admin
    pub fn admin(&self) -> bool {
        self.admin
    }
    ///Sets whether this user is an admin, returning `&mut Self` for chaining
    pub fn set_admin(&mut self, admin: bool) -> &mut Self {
        self.admin = admin;
        self
    }
    ///Borrows the user's name
    pub(crate) fn name(&self) -> &str {
        &*self.name
    }
    ///Sets the user's name, returning `&mut Self` for chaining
    pub(crate) fn set_name(&mut self, name: String) -> &mut Self {
        self.name = name;
        self
    }
}

deref

If set to true, this opts the field into deref detection for common types if the struct or struct-method have turned deref = false. If set to false, this opts the specific field out of deref detection for common types, borrowing the owned type. If set to a specific type, dereference to the specific type. Some types such as [u8] will require quoting.

#[derive(fieldwork::Fieldwork)]
#[fieldwork(get, get_mut)]
struct User {
    /// the user's name
    name: String,

    /// a small image in jpg format
    #[field(deref = "[u8]")]
    profile_thumbnail: Vec<u8>,

    // opt out of deref detection so we can use the arc directly
    #[field(deref = false)]
    an_arc: Arc<()>,
}
// GENERATED
impl User {
    ///Borrows the user's name
    pub fn name(&self) -> &str {
        &*self.name
    }
    ///Mutably borrow the user's name
    pub fn name_mut(&mut self) -> &mut str {
        &mut *self.name
    }
    ///Borrows a small image in jpg format
    pub fn profile_thumbnail(&self) -> &[u8] {
        &*self.profile_thumbnail
    }
    ///Mutably borrow a small image in jpg format
    pub fn profile_thumbnail_mut(&mut self) -> &mut [u8] {
        &mut *self.profile_thumbnail
    }
    pub fn an_arc(&self) -> &Arc<()> {
        &self.an_arc
    }
    pub fn an_arc_mut(&mut self) -> &mut Arc<()> {
        &mut self.an_arc
    }
}

option_set_some

Enable or disable automatic wrapping of setter values in Some() for this specific Option field with option_set_some = true or option_set_some = false. This allows you to override struct or struct-method level settings for individual fields.

#[derive(fieldwork::Fieldwork)]
#[fieldwork(set, with)]
struct User {
    /// Nickname - uses regular Option setter
    #[field(option_set_some = false)]
    nickname: Option<String>,
    
    /// Display name - uses automatic Some() wrapping
    #[field(option_set_some = true)]
    display_name: Option<String>,
}
// GENERATED
impl User {
    ///Sets Nickname - uses regular Option setter, returning `&mut Self` for chaining
    pub fn set_nickname(&mut self, nickname: Option<String>) -> &mut Self {
        self.nickname = nickname;
        self
    }
    ///Owned chainable setter for Nickname - uses regular Option setter, returning `Self`
    #[must_use]
    pub fn with_nickname(mut self, nickname: Option<String>) -> Self {
        self.nickname = nickname;
        self
    }
    ///Sets Display name - uses automatic Some() wrapping, returning `&mut Self` for chaining
    pub fn set_display_name(&mut self, display_name: String) -> &mut Self {
        self.display_name = Some(display_name);
        self
    }
    ///Owned chainable setter for Display name - uses automatic Some() wrapping, returning `Self`
    #[must_use]
    pub fn with_display_name(mut self, display_name: String) -> Self {
        self.display_name = Some(display_name);
        self
    }
}

into

Enable or disable impl Into<T> parameters for setter methods for this specific field with into/into = true or into = false. This allows you to override struct or struct-method level settings for individual fields.

#[derive(fieldwork::Fieldwork)]
#[fieldwork(set, with)]
struct User {
    name: String,
    
    /// Display name - uses impl Into<String> parameter
    #[field(into)]
    display_name: String,
}
// GENERATED
impl User {
    pub fn set_name(&mut self, name: String) -> &mut Self {
        self.name = name;
        self
    }
    #[must_use]
    pub fn with_name(mut self, name: String) -> Self {
        self.name = name;
        self
    }
    ///Sets Display name - uses impl Into<String> parameter, returning `&mut Self` for chaining
    pub fn set_display_name(&mut self, display_name: impl Into<String>) -> &mut Self {
        self.display_name = display_name.into();
        self
    }
    ///Owned chainable setter for Display name - uses impl Into<String> parameter, returning `Self`
    #[must_use]
    pub fn with_display_name(mut self, display_name: impl Into<String>) -> Self {
        self.display_name = display_name.into();
        self
    }
}

option_borrow_inner

Opt out of Option detection for this field with option_borrow_inner = false, or if it has been opted out at the struct or struct method level, opt back in with option_borrow_inner or option_borrow_inner = true for a single field, as in #[field(option_borrow_inner)] or #[field(option_borrow_inner = true)]. See option_borrow_inner above for more information.

#[derive(fieldwork::Fieldwork)]
#[fieldwork(get, get_mut)]
struct User {
    profile_thumbnail: Option<Vec<u8>>,

    #[field(option_borrow_inner = false)] // opt out of option_borrow_inner
    nickname: Option<String>,
}
// GENERATED
impl User {
    pub fn profile_thumbnail(&self) -> Option<&[u8]> {
        self.profile_thumbnail.as_deref()
    }
    pub fn profile_thumbnail_mut(&mut self) -> Option<&mut [u8]> {
        self.profile_thumbnail.as_deref_mut()
    }
    pub fn nickname(&self) -> &Option<String> {
        &self.nickname
    }
    pub fn nickname_mut(&mut self) -> &mut Option<String> {
        &mut self.nickname
    }
}




§Field Method Configuration

name

Specify the full function name for this particular method. Note that this overrides both template and field-level name.

#[derive(fieldwork::Fieldwork)]
struct User {
    /// whether this user is an admin
    #[field(get(name = is_an_admin))]
    admin: bool,
}
// GENERATED
impl User {
    ///Returns a copy of whether this user is an admin
    pub fn is_an_admin(&self) -> bool {
        self.admin
    }
}

If there are no other configuration options needed, this can be provided with the following shortcut:

#[derive(fieldwork::Fieldwork)]
struct User {
    /// whether this user is an admin
    #[field(get = is_an_admin)]
    admin: bool,
}
// GENERATED
impl User {
    ///Returns a copy of whether this user is an admin
    pub fn is_an_admin(&self) -> bool {
        self.admin
    }
}

argument

Specify the name of the argument for this specific method and field.

#[derive(fieldwork::Fieldwork)]
struct User {
    /// whether this user is an admin
    #[field(set(argument = is_an_admin))]
    admin: bool,
}
// GENERATED
impl User {
    ///Sets whether this user is an admin, returning `&mut Self` for chaining
    pub fn set_admin(&mut self, is_an_admin: bool) -> &mut Self {
        self.admin = is_an_admin;
        self
    }
}

doc

Override the documentation for this specific method and field.

#[derive(fieldwork::Fieldwork)]
struct User {
    #[field(set(doc = "Specify whether this user can administer this system"))]
    admin: bool,
}
// GENERATED
impl User {
    ///Specify whether this user can administer this system
    pub fn set_admin(&mut self, admin: bool) -> &mut Self {
        self.admin = admin;
        self
    }
}

chain (set only)

To return () from this specific set method instead of &mut Self, provide chain = false.

#[derive(fieldwork::Fieldwork)]
struct User {
    /// whether this user is an admin
    #[field(set(chain = false))]
    admin: bool,
}
// GENERATED
impl User {
    ///Sets whether this user is an admin
    pub fn set_admin(&mut self, admin: bool) {
        self.admin = admin;
    }
}

copy (get only)

Sometimes it is more useful to return a Copy of the returned type instead of a borrow. To opt into this behavior for a specific field, use #[field(get(copy))]. To opt out of default copy behavior for common types such as bool, use #[field(get(copy = false))].

#[derive(fieldwork::Fieldwork)]
struct Collection {
    /// length
    #[field(get(copy))]
    len: usize,
}
// GENERATED
impl Collection {
    ///Returns a copy of length
    pub fn len(&self) -> usize {
        self.len
    }
}

skip

Omit this field from the particular method. As a shorthand, you can also use method = false (e.g., without = false) which is equivalent to without(skip).

#[derive(fieldwork::Fieldwork)]
#[fieldwork(get, set)]
struct User {
    /// whether this user is an admin
    #[field(set(skip))]
    admin: bool,

    /// the user's name
    name: String,
}
// GENERATED
impl User {
    ///Returns a copy of whether this user is an admin
    pub fn admin(&self) -> bool {
        self.admin
    }
    ///Borrows the user's name
    pub fn name(&self) -> &str {
        &*self.name
    }
    ///Sets the user's name, returning `&mut Self` for chaining
    pub fn set_name(&mut self, name: String) -> &mut Self {
        self.name = name;
        self
    }
}

deref

For get and get_mut, return this derefenced type for this specific method and field. Some types such as [u8] will require quoting. This can also be set to true or false to opt in or out of deref detection for common types.

#[derive(fieldwork::Fieldwork)]
#[fieldwork(deref = false)]
struct User {
    /// the user's name
    #[field(get(deref = str), set, get_mut)]
    name: String,

    /// a small image in jpg format
    #[field(get_mut(deref = true), get, set)]
    profile_thumbnail: Vec<u8>,
}
// GENERATED
impl User {
    ///Borrows the user's name
    pub fn name(&self) -> &str {
        &*self.name
    }
    ///Mutably borrow the user's name
    pub fn name_mut(&mut self) -> &mut String {
        &mut self.name
    }
    ///Sets the user's name, returning `&mut Self` for chaining
    pub fn set_name(&mut self, name: String) -> &mut Self {
        self.name = name;
        self
    }
    ///Borrows a small image in jpg format
    pub fn profile_thumbnail(&self) -> &Vec<u8> {
        &self.profile_thumbnail
    }
    ///Mutably borrow a small image in jpg format
    pub fn profile_thumbnail_mut(&mut self) -> &mut [u8] {
        &mut *self.profile_thumbnail
    }
    ///Sets a small image in jpg format, returning `&mut Self` for chaining
    pub fn set_profile_thumbnail(&mut self, profile_thumbnail: Vec<u8>) -> &mut Self {
        self.profile_thumbnail = profile_thumbnail;
        self
    }
}

option_set_some (set and with only)

Enable or disable automatic wrapping of setter values in Some() for a specific method and field. This provides the most granular control, allowing you to enable the feature for just the set method but not with, or vice versa.

#[derive(fieldwork::Fieldwork)]
#[fieldwork(set, with)]
struct User {
    /// Enable automatic Some() wrapping only for the set method
    #[field(set(option_set_some = true))]
    nickname: Option<String>,
}
// GENERATED
impl User {
    ///Sets Enable automatic Some() wrapping only for the set method, returning `&mut Self` for chaining
    pub fn set_nickname(&mut self, nickname: String) -> &mut Self {
        self.nickname = Some(nickname);
        self
    }
    ///Owned chainable setter for Enable automatic Some() wrapping only for the set method, returning `Self`
    #[must_use]
    pub fn with_nickname(mut self, nickname: Option<String>) -> Self {
        self.nickname = nickname;
        self
    }
}

into (set and with only)

Enable or disable impl Into<T> parameters for a specific method and field. This provides the most granular control, allowing you to enable the feature for just the set method but not with, or vice versa.

#[derive(fieldwork::Fieldwork)]
#[fieldwork(set, with)]
struct User {
    /// Enable impl Into<String> only for the set method
    #[field(set(into))]
    name: String,
}
// GENERATED
impl User {
    ///Sets Enable impl Into<String> only for the set method, returning `&mut Self` for chaining
    pub fn set_name(&mut self, name: impl Into<String>) -> &mut Self {
        self.name = name.into();
        self
    }
    ///Owned chainable setter for Enable impl Into<String> only for the set method, returning `Self`
    #[must_use]
    pub fn with_name(mut self, name: String) -> Self {
        self.name = name;
        self
    }
}

option_borrow_inner

Opt out of Option detection for this field and method with #[field(option_borrow_inner = false)], or if it has been opted out at the struct, struct method, or field level, opt back in with option_borrow_inner or option_borrow_inner = true for a single field and method, as in #[field(get(option_borrow_inner))] or #[field(get_mut(option_borrow_inner = true))]. See option_borrow_inner above for more information.

#[derive(fieldwork::Fieldwork)]
#[fieldwork(get, get_mut)]
struct User {
    profile_thumbnail: Option<Vec<u8>>,

    // opt out of option_borrow_inner just for get_mut, so we can use Option::insert or similar
    #[field(get_mut(option_borrow_inner = false))]
    nickname: Option<String>,
}
// GENERATED
impl User {
    pub fn profile_thumbnail(&self) -> Option<&[u8]> {
        self.profile_thumbnail.as_deref()
    }
    pub fn profile_thumbnail_mut(&mut self) -> Option<&mut [u8]> {
        self.profile_thumbnail.as_deref_mut()
    }
    pub fn nickname(&self) -> Option<&str> {
        self.nickname.as_deref()
    }
    pub fn nickname_mut(&mut self) -> &mut Option<String> {
        &mut self.nickname
    }
}




§How fieldwork selects which methods to generate for which fields

In order to be maximally expressive, fieldwork can operate in both opt-in and opt-out mode. #[derive(fieldwork::Fieldwork)] does nothing without at least one #[fieldwork]/#[field] attribute.

§Opt-out

If a #[fieldwork(get, set, with, without, get_mut)] attribute is applied to the struct, it applies those methods to all fields that don’t have #[field(skip)] (to skip the entire field) or, using get as an example, #[field(get(skip)] or #[field(get = false)] to skip just the get method for the particular field.

§Opt-in

It is also possible to omit the struct-level attribute and opt individual fields in with eg #[field(get, set)].

If you need to specify struct-level configuration in order to reduce repetition but still want to operate in an opt-in mode instead of using skip, fieldwork supports opt_in as a top level argument. It is also possible to specify opt_in at a field level, which will only include the methods specified on that field.

#[derive(fieldwork::Fieldwork)]
#[fieldwork(opt_in, get(template = "get_{}"))]
struct User {
    /// whether this user is an admin
    #[field = true]
    admin: bool,

    /// the user's name
    #[field(set)]
    name: String,

    private: ()
}
// GENERATED
impl User {
    ///Returns a copy of whether this user is an admin
    pub fn get_admin(&self) -> bool {
        self.admin
    }
    ///Sets the user's name, returning `&mut Self` for chaining
    pub fn set_name(&mut self, name: String) -> &mut Self {
        self.name = name;
        self
    }
}

§License

Licensed under either of Apache License, Version 2.0 or MIT license at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this crate by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

Derive Macros§

Fieldwork
see crate-level documentation