Rust程式語言內建的derive屬性可以快速地替我們的型別加上內建的特性,不過會要求該型別內的欄位也都要先實作相同的特性,而且很多時候利用derive屬性實作出來的特性並不能達到我們期望的功能。



好比說以下這個用來作為唯讀資料庫的資料模型的結構體:

#[derive(PartialEq, Eq, Hash)]
pub struct Person {
    uid: u64,
    name: String,
    age: u8
}

我們希望這個Person結構體可以被雜湊,且用有判斷兩個Person結構體是否相同的功能。以上的實作方式雖然在使用起來並沒有什麼太大的問題,但是效能不會很好,因為透過derive屬性實作的Hash特性,會去計算所有欄位的雜湊值,然而在這裡我們其實只要去計算uid這個欄位的雜湊值就可以了。同理,PartialEq特性也是一樣只需要去判斷uid這個欄位是否PartialEq就好了,實在不需要用上所有欄位。

再來,如果我們加了某個沒有同時實作HashPartialEqEq特性的欄位,這個Person結構體就沒有辦法被編譯了。

例如:

#[derive(PartialEq, Eq, Hash)]
pub struct Person {
    uid: u64,
    name: String,
    age: u8,
    score: f64,
}

以上程式,由於型別為f64score欄位沒有實作Eq特性,更不用說有去實作Hash特性了,所以會編譯失敗。

還有,除了無法靈活調整特性實作外,derive屬性能實作的特性種類也有限。像是FromInto特性就無法實作。

derive屬性也可能會替泛型參數加上過多的約束,例如:

struct NewType<T>(T);

impl<T> Hash for NewType<T> {
    fn hash<H: Hasher>(&self, state: &mut H) {
        0u8.hash(state)
    }
}

#[derive(Hash)]
struct B<T> {
    f1: NewType<T>,
}

以上程式,B<T>會在T: Hash的約束下被實作Hash特性。然而實作給NewType結構體的Hash特性並沒有在T泛型參數上添加約束,因此derive屬性的約束添加可以說是畫蛇添足了。

Educe

「Educe」是筆者開發的程序式巨集套件,可以用來加強原先Rust程式語言用derive屬性來實作內建特性的方式,使得這類程式序巨集可以在更多常見的場景下直接使用,讓開發者不需要手動去寫impl相關的程式。

Crates.io

Cargo.toml

educe = "*"

使用方法

Educe目前能夠實作的特性有DebugCloneCopyPartialEqEqPartialOrdOrdHashDefaultDerefDerefMutInto

上面的實作Hash特性的例子可以很輕易地用Educe來改寫,程式如下:

use educe::Educe;

#[derive(Educe)]
#[educe(PartialEq, Eq, Hash)]
pub struct Person {
    uid: u64,
    #[educe(Eq(ignore), Hash(ignore))]
    name: String,
    #[educe(Eq(ignore), Hash(ignore))]
    age: u8,
    #[educe(Eq(ignore), Hash(ignore))]
    score: f64,
}

struct NewType<T>(T);

impl<T> Hash for NewType<T> {
    fn hash<H: Hasher>(&self, state: &mut H) {
        0u8.hash(state)
    }
}

#[derive(Educe)]
#[educe(Hash)]
struct B<T> {
    f1: NewType<T>,
}

// impl<T> Hash for B<T>, not impl<T: Hash> Hash for B<T>
特色

在預設的情形下,Educe會啟用所有有支援的特性。如果想要禁用它們的話,在Cargo.toml設定檔中,可以不啟用預設特性,只把自己想要用的特性加入features中來啟用就好。

如下:

[dependencies.educe]
version = "*"
features = ["Debug", "Default", "Hash", "Clone", "Copy"]
default-features = false
Debug

在型別上加上#[derive(Educe)]#[educe(Debug)]屬性,就可以替該型別實作Debug特性。型別可以是任意結構體、任意列舉和任意聯合(union)。此外,它還有支援型別名稱、變體名稱和欄位名稱的改變,也能夠忽略掉指定的欄位,還能夠替指定的欄位設定格式化時要使用的方法。更方便的是,它還能夠把一般結構體當作元組結構體(tuple struct)來格式化,或是反過來把元組結構體當作一般結構體來格式化。

基本用法
use educe::Educe;

#[derive(Educe)]
#[educe(Debug)]
struct Struct {
    f1: u8
}

#[derive(Educe)]
#[educe(Debug)]
enum Enum {
    V1,
    V2 {
        f1: u8,
    },
    V3(u8),
}
改變型別名稱、變體名稱或是欄位名稱

name參數可以用來重新命名一個型別、變體或是欄位。如果你把它設為false則可以隱藏欄位名稱;把它設為true則可以強制顯示名稱。

use educe::Educe;

#[derive(Educe)]
#[educe(Debug(name(Struct2)))]
struct Struct {
    #[educe(Debug(name(f)))]
    f1: u8
}

#[derive(Educe)]
#[educe(Debug(name = true))]
enum Enum {
    #[educe(Debug(name = false))]
    V1,
    #[educe(Debug(name(V)))]
    V2 {
        #[educe(Debug(name(f)))]
        f1: u8,
    },
    #[educe(Debug(name = false))]
    V3(u8),
}
忽略欄位

ignore屬性可以用來忽略指定的欄位。

use educe::Educe;

#[derive(Educe)]
#[educe(Debug)]
struct Struct {
    #[educe(Debug(ignore))]
    f1: u8
}

#[derive(Educe)]
#[educe(Debug)]
enum Enum {
    V1,
    V2 {
        #[educe(Debug(ignore))]
        f1: u8,
    },
    V3(
        #[educe(Debug(ignore))]
        u8
    ),
}
元組結構體格式化成一般結構體,一般結構體格式化成元組結構體

named_field屬性可以用來設定型別或是變體下的欄位是否要擁有名稱。

use educe::Educe;

#[derive(Educe)]
#[educe(Debug(named_field = false))]
struct Struct {
    f1: u8
}

#[derive(Educe)]
#[educe(Debug)]
enum Enum {
    V1,
    #[educe(Debug(named_field = false))]
    V2 {
        f1: u8,
    },
    #[educe(Debug(named_field = true))]
    V3(
        u8,
        #[educe(Debug(name(value)))]
        i32
    ),
}
使用另外的方法來做格式化

method參數可以被用在欄位上,取代該欄位的Debug特性實作,使該欄位的型別不一定需要實作Debug特性。

use educe::Educe;

use std::fmt::{self, Formatter};

fn fmt<T>(_s: &T, f: &mut Formatter<'_>) -> fmt::Result {
    f.write_str("Hi")
}

#[derive(Educe)]
#[educe(Debug)]
enum Enum<T> {
    V1,
    V2 {
        #[educe(Debug(method(fmt)))]
        f1: u8,
    },
    V3(
        #[educe(Debug(method(std::fmt::UpperHex::fmt)))]
        u8,
        #[educe(Debug(method(fmt)))]
        T
    ),
}
泛型參數自動加上Debug特性約束,或是手動設定其它約束

泛型型別參數在有需要的情況下會自動被加上必須實作Debug特性的條件。

use educe::Educe;

#[derive(Educe)]
#[educe(Debug)]
enum Enum<T, K> {
    V1,
    V2 {
        f1: K,
    },
    V3(
        T
    ),
}

或者,您也可以自行撰寫where語法後的限定條件,如下:

use educe::Educe;

use std::fmt::{self, Formatter};

fn fmt<D>(_s: &D, f: &mut Formatter<'_>) -> fmt::Result {
    f.write_str("Hi")
}

#[derive(Educe)]
#[educe(Debug(bound(T: std::fmt::Debug)))]
enum Enum<T, K> {
    V1,
    V2 {
        #[educe(Debug(method(fmt)))]
        f1: K,
    },
    V3(
        T
    ),
}

在上面的程式例子中,T有綁定Debug特性,而K沒有。

或者,我們也可以讓Educe的行為跟derive屬性一樣,也就是替所有的泛型參數加上約束,不管結構體長怎樣:

use educe::Educe;

#[derive(Educe)]
#[educe(Debug(bound(*)))]
struct Struct<T> {
    #[educe(Debug(ignore))]
    f: T,
}

這樣的作法在我們不希望將特性實作成為永久公開 API 的一部分時會很有用。在上面這個例子中,Struct<T>不會實作Debug,除非T有實作Debug。也就是說,即使現在不需要,它也有一個T: Debug的約束。以後我們可能會想要在用Debug來格式化的時候顯示出f欄位,到時候就不需要通過添加這個約束來進行破壞性 API 更改。

聯合

聯合並不會依照其欄位的型別來做格式化,而是會將聯合在記憶體中的值以u8切片的方式來格式化,因為我們無法在程式執行階段知道當前的聯合究竟是在用哪個欄位。聯合中的欄位無法被省略、重新命名或是用其它的方法來格式化。替聯合型別實作Debug的方式是不安全的(unsafe),可能會暴露未經初始化的記憶體資料。

use educe::Educe;

#[derive(Educe)]
#[educe(Debug(unsafe))]
union Union {
    f1: u8,
    f2: i32,
}
Clone

在型別上加上#[derive(Educe)]#[educe(Clone)]屬性,就可以替該型別實作Clone特性。型別可以是任意結構體、任意列舉和任意聯合。此外,它還能夠替指定的欄位設定在複製時要使用的方法。

基本用法
use educe::Educe;

#[derive(Educe)]
#[educe(Clone)]
struct Struct {
    f1: u8
}

#[derive(Educe)]
#[educe(Clone)]
enum Enum {
    V1,
    V2 {
        f1: u8,
    },
    V3(u8),
}
使用另外的方法來做複製

method參數可以被用在欄位上,取代該欄位的Clone特性實作。使該欄位的型別不一定需要實作Clone特性。

use educe::Educe;

fn clone(v: &u8) -> u8 {
    v + 100
}

trait A {
    fn add(&self, rhs: u8) -> Self;
}

fn clone2<T: A>(v: &T) -> T {
    v.add(100)
}

#[derive(Educe)]
#[educe(Clone)]
enum Enum<T: A> {
    V1,
    V2 {
        #[educe(Clone(method(clone)))]
        f1: u8,
    },
    V3(
        #[educe(Clone(method(clone2)))]
        T
    ),
}
泛型參數自動加上Clone特性約束,或是手動設定其它約束

泛型型別參數在有需要的情況下會自動被加上必須實作Clone特性的條件。如果#[educe(Copy)]屬性存在,則是會加上必須實作Copy特性的條件。

use educe::Educe;

#[derive(Educe)]
#[educe(Clone)]
enum Enum<T, K> {
    V1,
    V2 {
        f1: K,
    },
    V3(
        T
    ),
}

或者,您也可以自行撰寫where語法後的限定條件,如下:

use educe::Educe;

trait A {
    fn add(&self, rhs: u8) -> Self;
}

fn clone<T: A>(v: &T) -> T {
    v.add(100)
}

#[derive(Educe)]
#[educe(Clone(bound(T: std::clone::Clone)))]
enum Enum<T, K: A> {
    V1,
    V2 {
        #[educe(Clone(method(clone)))]
        f1: K,
    },
    V3(
        T
    ),
}

在上面的程式例子中,T有綁定Clone特性,而K沒有。

或者,我們也可以使用bound(*)讓Educe的行為跟derive屬性一樣,更詳細的說明請見Debug小節。

use educe::Educe;

trait A {
    fn add(&self, rhs: u8) -> Self;
}

fn clone<T: A>(v: &T) -> T {
    v.add(100)
}

#[derive(Educe)]
#[educe(Clone(bound(*)))]
struct Struct<T: A> {
    #[educe(Clone(method(clone)))]
    f: T,
}
聯合

聯合的Clone特性實作請參考以下實作Copy特性的小節。

Copy

在型別上加上#[derive(Educe)]#[educe(Copy)]屬性,就可以替該型別實作Copy特性。型別可以是任意結構體、任意列舉和任意聯合。

基本用法
use educe::Educe;

#[derive(Educe)]
#[educe(Copy, Clone)]
struct Struct {
    f1: u8
}

#[derive(Educe)]
#[educe(Copy, Clone)]
enum Enum {
    V1,
    V2 {
        f1: u8,
    },
    V3(u8),
}
泛型參數自動加上Copy特性約束,或是手動設定其它約束

所有的泛型型別參數會自動被加上必須實作Copy特性的條件。

use educe::Educe;

#[derive(Educe)]
#[educe(Copy, Clone)]
enum Enum<T, K> {
    V1,
    V2 {
        f1: K,
    },
    V3(
        T
    ),
}

或者,您也可以自行撰寫where語法後的限定條件,如下:

use educe::Educe;

trait A {
    fn add(&self, rhs: u8) -> Self;
}

fn clone<T: A>(v: &T) -> T {
    v.add(100)
}

#[derive(Educe)]
#[educe(Copy, Clone(bound(T: Copy, K: A + Copy)))]
enum Enum<T, K> {
    V1,
    V2 {
        #[educe(Clone(method(clone)))]
        f1: K,
    },
    V3(
        T
    ),
}

請注意使用自己的方法來取代Clone特性的實作的話,對於又實作Copy特性的型別來說是不合適的。

聯合

#[educe(Copy, Clone)]屬性可以被用在任意聯合上。聯合中的欄位無法用其它的方法來複製。

use educe::Educe;

#[derive(Educe)]
#[educe(Copy, Clone)]
union Union {
    f1: u8,
}
PartialEq

在型別上加上#[derive(Educe)]#[educe(PartialEq)]屬性,就可以替該型別實作PartialEq特性。型別可以是任意結構體、任意列舉和任意聯合。此外,它還能夠忽略掉指定的欄位,也能夠替指定的欄位設定在比較是否相同時要使用的方法。

基本用法
use educe::Educe;

#[derive(Educe)]
#[educe(PartialEq)]
struct Struct {
    f1: u8
}

#[derive(Educe)]
#[educe(PartialEq)]
enum Enum {
    V1,
    V2 {
        f1: u8,
    },
    V3(u8),
}
忽略欄位

ignore屬性可以用來忽略指定的欄位。

use educe::Educe;

#[derive(Educe)]
#[educe(PartialEq)]
struct Struct {
    #[educe(PartialEq(ignore))]
    f1: u8
}

#[derive(Educe)]
#[educe(PartialEq)]
enum Enum {
    V1,
    V2 {
        #[educe(PartialEq(ignore))]
        f1: u8,
    },
    V3(
        #[educe(PartialEq(ignore))]
        u8
    ),
}
使用另外的方法來做比較

method參數可以被用在欄位上,取代該欄位的PartialEq特性實作。使該欄位的型別不一定需要實作PartialEq特性。

use educe::Educe;

fn eq(a: &u8, b: &u8) -> bool {
    a + 1 == *b
}

trait A {
    fn is_same(&self, other: &Self) -> bool;
}

fn eq2<T: A>(a: &T, b: &T) -> bool {
    a.is_same(b)
}

#[derive(Educe)]
#[educe(PartialEq)]
enum Enum<T: A> {
    V1,
    V2 {
        #[educe(PartialEq(method(eq)))]
        f1: u8,
    },
    V3(
        #[educe(PartialEq(method(eq2)))]
        T
    ),
}
泛型參數自動加上PartialEq特性約束,或是手動設定其它約束

泛型型別參數在有需要的情況下會自動被加上必須實作PartialEq特性的條件。

use educe::Educe;

#[derive(Educe)]
#[educe(PartialEq)]
enum Enum<T, K> {
    V1,
    V2 {
        f1: K,
    },
    V3(
        T
    ),
}

或者,您也可以自行撰寫where語法後的限定條件,如下:

use educe::Educe;

trait A {
    fn is_same(&self, other: &Self) -> bool;
}

fn eq<T: A>(a: &T, b: &T) -> bool {
    a.is_same(b)
}

#[derive(Educe)]
#[educe(PartialEq(bound(T: std::cmp::PartialEq, K: A)))]
enum Enum<T, K> {
    V1,
    V2 {
        #[educe(PartialEq(method(eq)))]
        f1: K,
    },
    V3(
        T
    ),
}

在上面的程式例子中,T有綁定PartialEq特性,而K沒有。

或者,我們也可以使用bound(*)讓Educe的行為跟derive屬性一樣,更詳細的說明請見Debug小節。

use educe::Educe;

#[derive(Educe)]
#[educe(PartialEq(bound(*)))]
struct Struct<T> {
    #[educe(PartialEq(ignore))]
    f: T,
}
聯合

#[educe(PartialEq(unsafe))]屬性可以被用在任意聯合上。聯合中的欄位無法用其它的方法來比較。替聯合型別實作PartialEq的方式是不安全的(unsafe),因為不會去檢查聯合究竟是使用使用哪個欄位。

use educe::Educe;

#[derive(Educe)]
#[educe(PartialEq(unsafe))]
union Union {
    f1: u8,
    f2: i32
}
Eq

在型別上加上#[derive(Educe)]#[educe(Eq)]屬性,就可以替該型別實作Eq特性。型別可以是任意結構體、任意列舉和任意聯合。此外,它還能夠忽略掉指定的欄位,也能夠替指定的欄位設定在比較是否相同時要使用的方法。

基本用法
use educe::Educe;

#[derive(Educe)]
#[educe(PartialEq, Eq)]
struct Struct {
    f1: u8
}

#[derive(Educe)]
#[educe(PartialEq, Eq)]
enum Enum {
    V1,
    V2 {
        f1: u8,
    },
    V3(u8),
}
忽略欄位

ignore屬性可以用來忽略指定的欄位。

use educe::Educe;

#[derive(Educe)]
#[educe(PartialEq, Eq)]
struct Struct {
    #[educe(Eq(ignore))]
    f1: u8
}

#[derive(Educe)]
#[educe(PartialEq, Eq)]
enum Enum {
    V1,
    V2 {
        #[educe(Eq(ignore))]
        f1: u8,
    },
    V3(
        #[educe(Eq(ignore))]
        u8
    ),
}
使用另外的方法來做比較

method參數可以被用在欄位上,取代該欄位的Eq特性實作。使該欄位的型別不一定需要實作PartialEq特性。

use educe::Educe;

fn eq(a: &u8, b: &u8) -> bool {
    a + 1 == *b
}

trait A {
    fn is_same(&self, other: &Self) -> bool;
}

fn eq2<T: A>(a: &T, b: &T) -> bool {
    a.is_same(b)
}

#[derive(Educe)]
#[educe(PartialEq, Eq)]
enum Enum<T: A> {
    V1,
    V2 {
        #[educe(Eq(method(eq)))]
        f1: u8,
    },
    V3(
        #[educe(Eq(method(eq2)))]
        T
    ),
}
泛型參數自動加上PartialEq特性約束,或是手動設定其它約束

泛型型別參數在有需要的情況下會自動被加上必須實作PartialEq特性的條件。

use educe::Educe;

#[derive(Educe)]
#[educe(PartialEq, Eq)]
enum Enum<T, K> {
    V1,
    V2 {
        f1: K,
    },
    V3(
        T
    ),
}

或者,您也可以自行撰寫where語法後的限定條件,如下:

use educe::Educe;

trait A {
    fn is_same(&self, other: &Self) -> bool;
}

fn eq<T: A>(a: &T, b: &T) -> bool {
    a.is_same(b)
}

#[derive(Educe)]
#[educe(PartialEq(bound(T: std::cmp::PartialEq, K: A)), Eq)]
enum Enum<T, K> {
    V1,
    V2 {
        #[educe(Eq(method(eq)))]
        f1: K,
    },
    V3(
        T
    ),
}
聯合

#[educe(PartialEq(unsafe), Eq)]屬性可以被用在任意聯合上。聯合中的欄位無法用其它的方法來比較。替聯合型別實作PartialEq的方式是不安全的(unsafe),因為不會去檢查聯合究竟是使用使用哪個欄位。

use educe::Educe;

#[derive(Educe)]
#[educe(PartialEq(unsafe), Eq)]
union Union {
    f1: u8,
    f2: i32
}
PartialOrd

在型別上加上#[derive(Educe)]#[educe(PartialOrd)]屬性,就可以替該型別實作PartialOrd特性。型別可以是任意結構體和任意列舉。此外,它還能夠忽略掉指定的欄位,也能夠替指定的欄位設定在比較是否相同時要使用的方法。另外,變體和欄位也都可以自訂排列順序。

基本用法
use educe::Educe;

#[derive(PartialEq, Educe)]
#[educe(PartialOrd)]
struct Struct {
    f1: u8
}

#[derive(PartialEq, Educe)]
#[educe(PartialOrd)]
enum Enum {
    V1,
    V2 {
        f1: u8,
    },
    V3(u8),
}
忽略欄位

ignore屬性可以用來忽略指定的欄位。

use educe::Educe;

#[derive(PartialEq, Educe)]
#[educe(PartialOrd)]
struct Struct {
    #[educe(PartialOrd(ignore))]
    f1: u8
}

#[derive(PartialEq, Educe)]
#[educe(PartialOrd)]
enum Enum {
    V1,
    V2 {
        #[educe(PartialOrd(ignore))]
        f1: u8,
    },
    V3(
        #[educe(PartialOrd(ignore))]
        u8
    ),
}
使用另外的方法來做比較

method參數可以被用在欄位上,取代該欄位的PartialOrd特性實作。使該欄位的型別不一定需要實作PartialOrd特性。

use educe::Educe;

use std::cmp::Ordering;

fn partial_cmp(a: &u8, b: &u8) -> Option<Ordering> {
    if a > b {
        Some(Ordering::Less)
    } else if a < b {
        Some(Ordering::Greater)
    } else {
        Some(Ordering::Equal)
    }
}

trait A {
    fn value(&self) -> u8;
}

fn partial_cmp2<T: A>(a: &T, b: &T) -> Option<Ordering> {
    partial_cmp(&a.value(), &b.value())
}

#[derive(Educe)]
#[educe(PartialEq, PartialOrd)]
enum Enum<T: A> {
    V1,
    V2 {
        #[educe(PartialOrd(method(partial_cmp)))]
        f1: u8,
    },
    V3(
        #[educe(PartialOrd(method(partial_cmp2)))]
        T
    ),
}
排列順序

每個欄位都可以加上#[educe(PartialOrd(rank = priority_value))]屬性,其中的priority_value是一個整數,用來決定比較的優先順序(priority_value愈小就愈優先)。欄位的priority_value預設值與其被定義的序數(也就是第幾個欄位)有關,會從isize::MIN開始算起。

use educe::Educe;

#[derive(PartialEq, Educe)]
#[educe(PartialOrd)]
struct Struct {
    #[educe(PartialOrd(rank = 1))]
    f1: u8,
    #[educe(PartialOrd(rank = 0))]
    f2: u8,
}

對於變體,可以要改變其排列優先順序,可以直接去設定它的判別值(discriminant)。

use educe::Educe;

#[derive(PartialEq, Educe)]
#[educe(PartialOrd)]
#[repr(u8)]
enum Enum {
    Three { f1: u8 } = 3,
    Two(u8) = 2,
    One = 1,
}
泛型參數自動加上PartialOrd特性約束,或是手動設定其它約束

泛型型別參數在有需要的情況下會自動被加上必須實作PartialOrd特性的條件。

use educe::Educe;

#[derive(PartialEq, Educe)]
#[educe(PartialOrd)]
enum Enum<T, K> {
    V1,
    V2 {
        f1: K,
    },
    V3(
        T
    ),
}

或者,您也可以自行撰寫where語法後的限定條件,如下:

use educe::Educe;

use std::cmp::Ordering;

trait A {
    fn value(&self) -> u8;
}

fn partial_cmp<T: A>(a: &T, b: &T) -> Option<Ordering> {
    a.value().partial_cmp(&b.value())
}

#[derive(PartialEq, Educe)]
#[educe(PartialOrd(bound(T: std::cmp::PartialOrd, K: PartialEq + A)))]
enum Enum<T, K> {
    V1,
    V2 {
        #[educe(PartialOrd(method(partial_cmp)))]
        f1: K,
    },
    V3(
        T
    ),
}

在上面的程式例子中,T有綁定PartialOrd特性,而K沒有。

或者,我們也可以使用bound(*)讓Educe的行為跟derive屬性一樣,更詳細的說明請見Debug小節。

use educe::Educe;

#[derive(PartialEq, Educe)]
#[educe(PartialOrd(bound(*)))]
struct Struct<T> {
    #[educe(PartialOrd(ignore))]
    f: T,
}
Ord

在型別上加上#[derive(Educe)]#[educe(Ord)]屬性,就可以替該型別實作Ord特性。型別可以是任意結構體和任意列舉。此外,它還能夠忽略掉指定的欄位,也能夠替指定的欄位設定在比較是否相同時要使用的方法。另外,變體和欄位也都可以自訂排列順序。

基本用法
use educe::Educe;

#[derive(PartialEq, Eq, Educe)]
#[educe(PartialOrd, Ord)]
struct Struct {
    f1: u8
}

#[derive(PartialEq, Eq, Educe)]
#[educe(PartialOrd, Ord)]
enum Enum {
    V1,
    V2 {
        f1: u8,
    },
    V3(u8),
}
忽略欄位

ignore屬性可以用來忽略指定的欄位。

use educe::Educe;

#[derive(PartialEq, Eq, Educe)]
#[educe(PartialOrd, Ord)]
struct Struct {
    #[educe(Ord(ignore))]
    f1: u8
}

#[derive(PartialEq, Eq, Educe)]
#[educe(PartialOrd, Ord)]
enum Enum {
    V1,
    V2 {
        #[educe(Ord(ignore))]
        f1: u8,
    },
    V3(
        #[educe(Ord(ignore))]
        u8
    ),
}
使用另外的方法來做比較

method參數可以被用在欄位上,取代該欄位的Ord特性實作。使該欄位的型別不一定需要實作Ord特性。

use educe::Educe;

use std::cmp::Ordering;

fn cmp(a: &u8, b: &u8) -> Ordering {
    if a > b {
        Ordering::Less
    } else if a < b {
        Ordering::Greater
    } else {
        Ordering::Equal
    }
}

trait A {
    fn value(&self) -> u8;
}

fn cmp2<T: A>(a: &T, b: &T) -> Ordering {
    cmp(&a.value(), &b.value())
}

#[derive(Educe)]
#[educe(PartialEq, Eq, PartialOrd, Ord)]
enum Enum<T: A> {
    V1,
    V2 {
        #[educe(Ord(method(cmp)))]
        f1: u8,
    },
    V3(
        #[educe(Ord(method(cmp2)))]
        T
    ),
}
排列順序

每個欄位都可以加上#[educe(Ord(rank = priority_value))]屬性,其中的priority_value是一個整數,用來決定比較的優先順序(priority_value愈小就愈優先)。欄位的priority_value預設值與其被定義的序數(也就是第幾個欄位)有關,會從isize::MIN開始算起。

use educe::Educe;

#[derive(PartialEq, Eq, Educe)]
#[educe(PartialOrd, Ord)]
struct Struct {
    #[educe(Ord(rank = 1))]
    f1: u8,
    #[educe(Ord(rank = 0))]
    f2: u8,
}

對於變體,可以要改變其排列優先順序,可以直接去設定它的判別值(discriminant)。

use educe::Educe;

#[derive(PartialEq, Eq, Educe)]
#[educe(PartialOrd, Ord)]
#[repr(u8)]
enum Enum {
    Three { f1: u8 } = 3,
    Two(u8) = 2,
    One = 1,
}
泛型參數自動加上Ord特性約束,或是手動設定其它約束

泛型型別參數在有需要的情況下會自動被加上必須實作Ord特性的條件。

use educe::Educe;

#[derive(PartialEq, Eq, Educe)]
#[educe(PartialOrd, Ord)]
enum Enum<T, K> {
    V1,
    V2 {
        f1: K,
    },
    V3(
        T
    ),
}

或者,您也可以自行撰寫where語法後的限定條件,如下:

use educe::Educe;

use std::cmp::Ordering;

trait A {
    fn value(&self) -> u8;
}

fn cmp<T: A>(a: &T, b: &T) -> Ordering {
    a.value().cmp(&b.value())
}

#[derive(PartialEq, Eq, Educe)]
#[educe(PartialOrd, Ord(bound(T: std::cmp::Ord, K: std::cmp::Ord + A)))]
enum Enum<T, K> {
    V1,
    V2 {
        #[educe(PartialOrd(method(cmp)))]
        f1: K,
    },
    V3(
        T
    ),
}
Hash

在型別上加上#[derive(Educe)]#[educe(Hash)]屬性,就可以替該型別實作Hash特性。型別可以是任意結構體、任意列舉和任意聯合。此外,它還能夠忽略掉指定的欄位,也能夠替指定的欄位設定雜湊時要使用的方法。

基本用法
use educe::Educe;

#[derive(Educe)]
#[educe(Hash)]
struct Struct {
    f1: u8
}

#[derive(Educe)]
#[educe(Hash)]
enum Enum {
    V1,
    V2 {
        f1: u8,
    },
    V3(u8),
}
忽略欄位

ignore屬性可以用來忽略指定的欄位。

use educe::Educe;

#[derive(Educe)]
#[educe(Hash)]
struct Struct {
    #[educe(Hash(ignore))]
    f1: u8
}

#[derive(Educe)]
#[educe(Hash)]
enum Enum {
    V1,
    V2 {
        #[educe(Hash(ignore))]
        f1: u8,
    },
    V3(
        #[educe(Hash(ignore))]
        u8
    ),
}
使用另外的方法來做雜湊

method參數可以被用在欄位上,取代該欄位的Hash特性實作。使該欄位的型別不一定需要實作Hash特性。

use educe::Educe;

use std::hash::{Hash, Hasher};

fn hash<H: Hasher>(_s: &u8, state: &mut H) {
    Hash::hash(&100, state)
}

fn hash2<H: Hasher, T>(_s: &T, state: &mut H) {
    Hash::hash(&100, state)
}

#[derive(Educe)]
#[educe(Hash)]
enum Enum<T> {
    V1,
    V2 {
        #[educe(Hash(method(hash)))]
        f1: u8,
    },
    V3(
        #[educe(Hash(method(hash2)))]
        T
    ),
}
泛型參數自動加上Hash特性約束,或是手動設定其它約束

泛型型別參數在有需要的情況下會自動被加上必須實作Ord特性的條件。

use educe::Educe;

#[derive(Educe)]
#[educe(Hash)]
enum Enum<T, K> {
    V1,
    V2 {
        f1: K,
    },
    V3(
        T
    ),
}

或者,您也可以自行撰寫where語法後的限定條件,如下:

use educe::Educe;

use std::hash::{Hash, Hasher};

trait A {
    fn value(&self) -> u8;
}

fn hash<H: Hasher, T: A>(s: &T, state: &mut H) {
    Hash::hash(&s.value(), state)
}

#[derive(Educe)]
#[educe(Hash(bound(T: std::hash::Hash, K: A)))]
enum Enum<T, K> {
    V1,
    V2 {
        #[educe(Hash(method(hash)))]
        f1: K,
    },
    V3(
        T
    ),
}

在上面的程式例子中,T有綁定Hash特性,而K沒有。

或者,我們也可以使用bound(*)讓Educe的行為跟derive屬性一樣,更詳細的說明請見Debug小節。

use educe::Educe;

#[derive(Educe)]
#[educe(Hash(bound(*)))]
struct Struct<T> {
    #[educe(Hash(ignore))]
    f: T,
}
聯合

#[educe(PartialEq(unsafe), Eq, Hash(unsafe))]屬性可以被用在任意聯合上。聯合中的欄位無法用其它的方法來比較。替聯合型別實作Hash的方式是不安全的(unsafe),因為不會去檢查聯合究竟是使用使用哪個欄位。

use educe::Educe;

#[derive(Educe)]
#[educe(PartialEq(unsafe), Eq, Hash(unsafe))]
union Union {
    f1: u8,
    f2: i32
}
Default

在型別上加上#[derive(Educe)]#[educe(Default)]屬性,就可以替該型別實作Default特性。型別可以是任意結構體、任意列舉和任意聯合。此外,它還能直接指定整個型別的預設值,或是型別內特定欄位的預設值。

基本用法

對於列舉和聯合,您必須要再指派一個列舉的變體或是一個聯合的欄位來當作預設值,除非該列舉的變體數量或是聯合的欄位數量剛好只有一個。

use educe::Educe;

#[derive(Educe)]
#[educe(Default)]
struct Struct {
    f1: u8
}

#[derive(Educe)]
#[educe(Default)]
enum Enum {
    V1,
    #[educe(Default)]
    V2 {
        f1: u8,
    },
    V3(u8),
}

#[derive(Educe)]
#[educe(Default)]
union Union {
    f1: u8,
    #[educe(Default)]
    f2: f64,
}
整個型別的預設值
use educe::Educe;

#[derive(Educe)]
#[educe(Default(expression = Struct { f1: 1 }))]
struct Struct {
    f1: u8
}

#[derive(Educe)]
#[educe(Default(expression = Enum::Struct { f1: 1 }))]
enum Enum {
    Unit,
    Struct {
        f1: u8
    },
    Tuple(u8),
}

#[derive(Educe)]
#[educe(Default(expression = Union { f1: 1 }))]
union Union {
    f1: u8,
    f2: f64,
}

您可能會需要啟用full特色來支援更進階的表達式解析。

指定欄位的預設值
use educe::Educe;

#[derive(Educe)]
#[educe(Default)]
struct Struct {
    #[educe(Default = 1)]
    f1: u8,
    #[educe(Default = 11111111111111111111111111111)]
    f2: i128,
    #[educe(Default = 1.1)]
    f3: f64,
    #[educe(Default = true)]
    f4: bool,
    #[educe(Default = "Hi")]
    f5: &'static str,
    #[educe(Default = "Hello")]
    f6: String,
    #[educe(Default = 'M')]
    f7: char,
}

#[derive(Educe)]
#[educe(Default)]
enum Enum {
    Unit,
    #[educe(Default)]
    Tuple(
        #[educe(Default(expression = 0 + 1))]
        u8,
        #[educe(Default(expression = -11111111111111111111111111111 * -1))]
        i128,
        #[educe(Default(expression = 1.0 + 0.1))]
        f64,
        #[educe(Default(expression = !false))]
        bool,
        #[educe(Default(expression = "Hi"))]
        &'static str,
        #[educe(Default(expression = String::from("Hello")))]
        String,
        #[educe(Default(expression = 'M'))]
        char,
    ),
}

#[derive(Educe)]
#[educe(Default)]
union Union {
    f1: u8,
    f2: i128,
    f3: f64,
    f4: bool,
    #[educe(Default = "Hi")]
    f5: &'static str,
    f6: char,
}
泛型參數自動加上Default特性約束,或是手動設定其它約束

泛型型別參數在有需要的情況下會自動被加上必須實作Default特性的條件。

use educe::Educe;

#[derive(Educe)]
#[educe(Default)]
enum Enum<T> {
    Unit,
    #[educe(Default)]
    Struct {
        f1: T
    },
    Tuple(T),
}

或者,您也可以自行撰寫where語法後的限定條件,如下:

use educe::Educe;

#[derive(Educe)]
#[educe(Default(bound(T: std::default::Default)))]
enum Enum<T> {
    Unit,
    #[educe(Default)]
    Struct {
        f1: T
    },
    Tuple(T),
}
new關聯函數

替型別加上#[educe(Default(new))]屬性,可以使它擁有一個new關聯函數。這個new關聯函數會去調用Default特性的default關聯函數。

use educe::Educe;

#[derive(Educe)]
#[educe(Default(new))]
struct Struct {
    f1: u8
}
Deref

在型別上加上#[derive(Educe)]#[educe(Deref)]屬性,就可以替該型別實作Deref特性。型別可以是任意結構體和任意列舉。

基本用法

您需要指定一個欄位作為預設取得的不可變參考的欄位,除非該結構體或是列舉的變體的欄位數量剛好只有一個。

use educe::Educe;

#[derive(Educe)]
#[educe(Deref)]
struct Struct {
    f1: u8,
    #[educe(Deref)]
    f2: u8,
}

#[derive(Educe)]
#[educe(Deref)]
enum Enum {
    Struct {
        f1: u8
    },
    Struct2 {
        f1: u8,
        #[educe(Deref)]
        f2: u8,
    },
    Tuple(u8),
    Tuple2(
        u8,
        #[educe(Deref)]
        u8
    ),
}
DerefMut

在型別上加上#[derive(Educe)]#[educe(DerefMut)]屬性,就可以替該型別實作DerefMut特性。型別可以是任意結構體和任意列舉。

基本用法

您需要指定一個欄位作為預設取得的可變參考的欄位,除非該結構體或是列舉的變體的欄位數量剛好只有一個。

use educe::Educe;

#[derive(Educe)]
#[educe(Deref, DerefMut)]
struct Struct {
    f1: u8,
    #[educe(Deref, DerefMut)]
    f2: u8,
}

#[derive(Educe)]
#[educe(Deref, DerefMut)]
enum Enum {
    Struct {
        f1: u8
    },
    Struct2 {
        f1: u8,
        #[educe(Deref, DerefMut)]
        f2: u8,
    },
    Tuple(u8),
    Tuple2(
        #[educe(DerefMut)]
        u8,
        #[educe(Deref)]
        u8
    ),
}

不可變參考欄位和可變參考欄位不一定要一樣,但是它們的型別必須要是相同的。

Into

在型別上加上#[derive(Educe)]#[educe(Into(type))]屬性,就可以替該型別實作Into<type>特性。型別可以是任意結構體和任意列舉。

基本用法

您需要指定一個欄位作為預設取得的可變參考的欄位,除非該結構體或是列舉的變體的欄位數量剛好只有一個。如果沒有指定,educe也會先嘗試尋找一個合適的欄位。

use educe::Educe;

#[derive(Educe)]
#[educe(Into(u8), Into(u16))]
struct Struct {
    f1: u8,
    f2: u16,
}

#[derive(Educe)]
#[educe(Into(u8))]
enum Enum {
    V1 {
        f1: u8,
        #[educe(Into(u8))]
        f2: u8,
    },
    V2 (
        u8
    ),
}
使用另外的方法來做轉換

method參數可以被用在欄位上,取代該欄位的Into特性實作。使該欄位的型別不一定需要實作Into特性。

use educe::Educe;

fn into(v: u16) -> u8 {
    v as u8
}

#[derive(Educe)]
#[educe(Into(u8))]
enum Enum {
    V1 {
        #[educe(Into(u8, method(into)))]
        f1: u16,
    },
    V2 (
        u8
    ),
}
泛型參數自動加上Into特性約束,或是手動設定其它約束

泛型型別參數在有需要的情況下會自動被加上必須實作Into<type>特性的條件。

use educe::Educe;

#[derive(Educe)]
#[educe(Into(u8))]
enum Enum<T, K> {
    V1 {
        f1: K,
    },
    V2 (
        T
    ),
}

或者,您也可以自行撰寫where語法後的限定條件,如下:

use educe::Educe;

fn into<T>(_v: T) -> u8 {
    0
}

#[derive(Educe)]
#[educe(Into(u8, bound(K: Into<u8>)))]
enum Enum<T, K> {
    V1 {
        f1: K,
    },
    V2 (
        #[educe(Into(u8, method(into)))]
        T
    ),
}