#traits #proc-macro #attributes #auto #auto-trait #enforces

macro must-implement-trait

An attribute proc-macro which enforces that a type (auto-)implements the specified trait(s)

1 unstable release

0.1.0 Aug 3, 2020

#2201 in Development tools

MIT license

9KB
91 lines

must-implement-trait

CI Badge

An attribute proc-macro which enforces that a type (auto-)implements the specified trait(s).

Usage

Apply the must_implement_trait attribute to your struct or enum:

use must_implement_trait::must_implement_trait;

#[must_implement_trait(Send)]
struct Resources {
    data: Rc<String>,
}

#[must_implement_trait(Send, UnwindSafe)]
enum ConfigSource {
    File(String),
    Database(DatabaseAdapter)
}

The attribute can take one or many trait names as parameters; it will enforce that all are implemented.

Motivation

Some traits, chiefly Send and Sync, are auto traits. This means that they are automatically implemented by the compiler, and rarely implemented (or un-implemented) by hand. Their implementations are implicit, and non-obvious to the reader without looking at documentation.

As a library author, you may want to guarantee to consumers that your exposed interface will carry these traits. It may also be helpful within one's own code to provoke an error near the source of the un-implementation rather than near a consumer which fails to compile due to a trait bound violation.

Consider the following example (examples/async-future-send.rs):

struct Resources {
    data: Rc<String>,
}

struct MyResourceManager {}

#[async_trait]
impl ResourceManager for MyResourceManager {
    async fn update_resources(resources: &Resources) {
        // Some code to update resources...
    }
    
}

We get an error within the update_resources function telling us:

error: future cannot be sent between threads safely
  --> examples/async-future-send.rs:21:54
   |
21 |       async fn update_resources(resources: &Resources) {
   |  ______________________________________________________^
22 | |         // Some code to update resources...
23 | |     }
   | |_____^ future returned by `__update_resources` is not `Send`
   |
   = help: within `Resources`, the trait `std::marker::Sync` is not implemented for `std::rc::Rc<std::string::String>`

By default, async-trait requires returned Futures to be Send. That is a rather natural choice, since async is often used in multi-threaded contexts. In this case, since we used an Rc rather than an Arc, Resources is not Send; we are given an error at the usage site. If Resources were in its own crate which is intended to be used in async contexts, the author may not realize they have made a breaking change by introducing an Rc.

In my experience, if working in a codebase which is async-oriented, it makes sense to ensure that most or all types are Send + Sync; it is easy to break downstream code if an auto-implementation changes implicitly.

By applying must_implement_trait, the error is moved to the type declaration:

error[E0277]: `std::rc::Rc<std::string::String>` cannot be sent between threads safely
  --> examples/async-future-send.rs:11:1
   |
11 | #[must_implement_trait(Send)]
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `std::rc::Rc<std::string::String>` cannot be sent between threads safely
   |
   = help: within `Resources`, the trait `std::marker::Send` is not implemented for `std::rc::Rc<std::string::String>`
   = note: required because it appears within the type `Resources`
   = help: see issue #48214
   = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info)

Dependencies

~1.5MB
~37K SLoC