Rust程式語言內建的derive
屬性可以快速地替我們的型別加上內建的特性,不過會要求該型別內的欄位也都要先實作相同的特性,而且很多時候利用derive
屬性實作出來的特性並不能達到我們期望的功能。
好比說以下這個用來作為唯讀資料庫的資料模型的結構體:
#[derive(PartialEq, Eq, Hash)]
pub struct Person {
uid: u64,
name: String,
age: u8
}
我們希望這個Person
結構體可以被雜湊,且用有判斷兩個Person
結構體是否相同的功能。以上的實作方式雖然在使用起來並沒有什麼太大的問題,但是效能不會很好,因為透過derive屬性實作的Hash
特性,會去計算所有欄位的雜湊值,然而在這裡我們其實只要去計算uid
這個欄位的雜湊值就可以了。同理,PartialEq
特性也是一樣只需要去判斷uid
這個欄位是否PartialEq
就好了,實在不需要用上所有欄位。
再來,如果我們加了某個沒有同時實作Hash
、PartialEq
和Eq
特性的欄位,這個Person
結構體就沒有辦法被編譯了。
例如:
#[derive(PartialEq, Eq, Hash)]
pub struct Person {
uid: u64,
name: String,
age: u8,
score: f64,
}
以上程式,由於型別為f64
的score
欄位沒有實作Eq
特性,更不用說有去實作Hash
特性了,所以會編譯失敗。
還有,除了無法靈活調整特性實作外,derive
屬性能實作的特性種類也有限。像是From
和Into
特性就無法實作。
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目前能夠實作的特性有Debug
、Clone
、Copy
、PartialEq
、Eq
、PartialOrd
、Ord
、Hash
、Default
、Deref
、DerefMut
、Into
。
上面的實作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
),
}