[][src]Module butcher::deriving_butcher_struct

Quick introduction to butchering on structs

It is sometimes needed to destructure an object, or to pattern-match over an enum. This can lead to a lot of code duplication when such objects are wrapped in Cow. The Butcher derive macro aims to generate such boilerplate automatically.

We'll use a simple example of a struct with the following declaration:

use butcher::Butcher;

#[derive(Butcher, Clone)]
struct Client {
    name: String,
    age: u8,
}

Destructing Client, when it is not wrapped in a Cow, is very easy:

use butcher::Butcher;

let c = Client {
    name: "Grace Hopper".to_string(),
    age: 85,
};

let Client { name, age } = c;

assert_eq!(name, "Grace Hopper");
assert_eq!(age, 85);

But once your Client is wrapped in a Cow, it becomes harder:

use butcher::Butcher;
use std::borrow::Cow;

let c_in_cow: Cow<Client> = Cow::Owned(Client {
    name: "Alan Turing".to_string(),
    age: 41,
});

let (name, age) = match c_in_cow {
    Cow::Owned(Client { name, age }) => (Cow::Owned(name), Cow::Owned(age)),
    Cow::Borrowed(Client { name, age }) => (Cow::Borrowed(name), Cow::Borrowed(age)),
};

assert_eq!(name, Cow::Borrowed("Alan Turing"));
assert_eq!(age, Cow::Borrowed(&41_u8));

Let's see how butcher can help up:

use butcher::Butcher;
use std::borrow::Cow;

let c_in_cow: Cow<Client> = Cow::Owned(Client {
    name: "Alan Turing".to_string(),
    age: 41,
});

let ButcheredClient { name, age } = Client::butcher(c_in_cow);

assert_eq!(name, Cow::Borrowed("Alan Turing"));
assert_eq!(age, Cow::Borrowed(&41_u8));

No more boilerplate involved. Neat!

If the compilation fails because some traits are required, don't panic, continue reading this page, the last section will solve your problems.

Configuration options

The Butcher procedural macro has been designed to allow special tricks, so that destructuring is more intuitive. Each struct field can be destructured with a destructuring method. They are all described in the next paragraphs.

You can use them like so:

use std::net::Ipv4Addr;
use butcher::Butcher;

#[derive(Butcher, Clone)]
struct Foo {
    #[butcher(regular)]
    a: Ipv4Addr,
    #[butcher(copy)]
    b: usize,
    #[butcher(as_deref)]
    c: String,
    #[butcher(unbox)]
    d: Box<Ipv4Addr>,
}

Regular

This method is used by default. If a field has type T, then the corresponding butchered field will have type Cow<T>.

See the documentation for Regular for more information.

Copy

This method will always copy the data (using the Clone trait), instead of returning a Cow. This can be used for type whose size is small, such as integers.

In the previous example, the field age of Client may be marked as copy.

See the documentation for Copy for more information.

As Deref

This method is used for situations when data can be represented both with its borrowed and its owned form. For instance, [T] is borrowed while Vec<T> is owned. Here, using as_deref on a field whose type is Vec<T> will convert it into Cow<[T]>.

See the documentation for AsDeref for more information.

Unbox

An usage of Box on sized types is to create recursive types. This butchering method will allow one to automatically get the data from the Box.

See the documentation for Unbox for more information.

Rebutcher

Sometimes it is necessary to butcher again a field of a butchered struct. This is what rebutcher does. This can be helpfull when it is necessay to destructure/pattern match on a struct contained in another struct.

See the documentation for Rebutcher for more information.

Retrieving the initial input type

The unbutcher allows to retrieve the initial data, in its owned form. It will move the associated butchered struct, and return the initial struct. Every borrowed data will be cloned if necessary.

This is particularly usefull when a catch-all match arm is needed.

use butcher::Butcher;
use std::borrow::Cow;

let c_in_cow: Cow<Client> = Cow::Owned(Client {
    name: "Alan Turing".to_string(),
    age: 41,
});

match Client::butcher(c_in_cow) {
    ButcheredClient { name, age } if name.as_ref() == "Abdul Alhazred" => {
        println!("Necronomicon author detected!")
    },
    ButcheredClient { name, age } if age == 255 => {
        println!("This person will soon reborn")
    },
    other => {
        // other has type ButcheredClient. Let's convert it to Client:
        let other = Client::unbutcher(other);
        println!("Hello {}", other.name);
    },
}

Fixing triggered compilation errors

While this proc macro generally generates code that compile on the first try, it may become tricky when generics are involved. The next section will show how to fix most errors.

Most of the errors raised when using the macro are trait bound-related. For instance, the following example does not compile:

This example deliberately fails to compile
use butcher::Butcher;

#[derive(Butcher, Clone)]
struct Foo<T> {
    #[butcher(as_deref)]
    elem: Vec<T>,
}

It gives us the following error:

error[E0277]: the trait bound `[T]: std::borrow::ToOwned` is not satisfied
 --> src/deriving_butcher_struct.rs:167:10
  |
6 | #[derive(Butcher, Clone)]
  |          ^^^^^^^ the trait `std::borrow::ToOwned` is not implemented for `[T]`
  |
  = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)

So here it is necessary to indicate that T must be Clone. It can be specified right after the butchering method:

use butcher::Butcher;

#[derive(Butcher, Clone)]
struct Foo<T> {
    #[butcher(as_deref, T: Clone)]
    elem: Vec<T>,
}

If multiple trait bounds must be specified, then they have to be separated by commas, using regular trait bounds syntax (eg: with the syntax of WhereClauseItem defined in the reference).