Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Optional Values

There is no need to explain what Option is in Rust and what role it plays. FieldX pays a bit of extra attention to it, though, because the concepts of optional values partially propagate into other patterns implemented by the crate. So, let's take this snippet as an example:

#[fxstruct]
struct Foo {
    #[fieldx(optional, get)]
    value: String,
}

It is quite apparent that the value field is optional and its final type is Option<String>. This is what a user gets. But along the lines, FieldX will use this information to generate proper code where its semantics depend on the optionality of the field. One case would be the builder implementation not failing with an error if an optional field is not set.

Predicate

In this chapter, we will discuss functionality that is more closely tied to optional values. Let's get back to the fact that there may be no value in the field. Normally, we'd check this with the is_none() method, but FieldX allows us to give the public API that our implementation provides a little touch of beauty:

#[fxstruct]
struct Foo {
    #[fieldx(optional, predicate, get)]
    value: String,
}

This gives us a has_value() method that returns true if the field is set:

let foo = Foo::new();
assert!(!foo.has_value());

Not satisfied with the name or it doesn't fit your API standards? predicate is a helper argument; so, no problem — give it another name!1

#[fxstruct]
struct Foo {
    #[fieldx(predicate("we_are_good"), get, set)]
    value: String,
}

And, of course:

let mut foo = Foo::new();
foo.set_value("Hello, world!".to_string());
assert!(foo.we_are_good());

This sample demonstrates another little perk of declaring optional fields with FieldX: there is no need to wrap the argument of the setter method into Some(), as it would be necessary with the explicit Option<String> approach.

Clearer

Where a value can be given, it can also be taken away. This is what the <a name="a003"></a>clearer argument is for1:

#[fxstruct]
struct Foo {
    #[fieldx(clearer, get)]
    value: String,
}

Let's combine this with the predicate argument and see how it works:

#[fxstruct]
struct Foo {
    #[fieldx(clearer, predicate, set)]
    value: String,
}

let mut foo = Foo::new();
assert!(!foo.has_value());
foo.set_value("Hello, world!".to_string());
assert!(foo.has_value());
let old_value = foo.clear_value();
assert!(!foo.has_value());
assert_eq!(old_value, Some("Hello, world!".to_string()));

Since clearer is a helper too, it can be renamed as well:

#[fxstruct]
struct Foo {
    #[fieldx(clearer("reset_value"), predicate, get)]
    value: String,
}

AsRef

Since by default the accessor methods return a reference to the field value, it is sometimes (often) gives us a situation where in order to do something with the Option it returns we need to convert it from &Option<T> to Option<&T>. Calling the as_ref() method every time is a bit tedious, so FieldX provides sub-argument as_ref for the get argument that does this for us automatically:

#[fxstruct]
struct Bar {
    #[fieldx(optional, get(as_ref), set)]
    value: String,
}

let mut bar = Bar::new();
bar.set_value("Привіт, світ!".to_string());
assert_eq!(bar.value(), Some(&"Привіт, світ!".to_string()));

Builder Pattern

Here is another reason why the optional keyword makes sense on its own. When generating the builder type, FieldX pays the same attention to the optionality of a field as it does to its default value:

use fieldx::fxstruct;

#[fxstruct(get, builder)]
struct Book {
    title:  String,
    author: String,
    year:   u32,
    #[fieldx(optional)]
    signed_by: String,
    #[fieldx(
        set("place_into"),
        inner_mut,
        default("unknown".to_string())
    )]
    location: String,
}

let book = Book::builder()
    .title("The Hitchhiker's Guide to the Galaxy".to_string())
    .author("Douglas Adams".to_string())
    .year(1979)
    .build()
    .expect("Failed to create book");

assert!(book.signed_by().is_none());

About the same result could be achieved with an explicit Option-typed field by giving it the explicit default(None) argument, but doesn't the above sample look way better in its conciseness and readability?


  1. Note that the optional keyword is omitted in this case because predicate and clear arguments imply that the field is optional. ↩2