#traits #macro-derive #macro #boilerplate #reduce-boilerplate #derive #sweet

no-std zoet

为减少实现常见特质时的冗余代码,添加了 #[zoet]

15 个版本

0.1.14 2024 年 7 月 30 日
0.1.12 2023 年 7 月 16 日
0.1.10 2022 年 10 月 29 日
0.1.9 2022 年 3 月 12 日
0.1.0 2019 年 7 月 24 日

222Rust 模式 中排名

Download history 99/week @ 2024-04-26 5/week @ 2024-05-03 194/week @ 2024-06-28 63/week @ 2024-07-05 229/week @ 2024-07-26 27/week @ 2024-08-02 2/week @ 2024-08-09

258 每月下载量
用于 catwalk-s-protobuf

MIT 许可证

23KB
166

为减少实现常见特质时的冗余代码,添加了 #[zoet] 宏。

如果你厌倦了编写 impl Deref for Bar 等,并且它因为与 AsRef 混淆而没有编译,因为实现了 PartialOrd 并错误地认为推导 Ord 会做正确的事情,而遇到难以调试的问题,或者你更愿意将这些核心特质作为常规函数在你的 impl Bar 中实现,就像较简单的语言一样,这个crate就是为你准备的!

zoet 在表面上类似于各种 derive 宏,例如 derive_more,但与基于结构体的内容生成特质不同,它基于单个函数生成特质。一个例子比任何文字描述都要好

use core::{
    cmp::Ordering,
    hash::{Hash, Hasher},
};
use zoet::zoet;

#[derive(Clone, Copy, Debug, Eq)]
struct Length(usize);
#[zoet]
impl Length {
    #[zoet(Default)] // generates `impl Default for Length`
    pub fn new() -> Self {
        Self(0)
    }

    #[zoet(From)] // generates `From<usize> for Length`
    fn from_usize(value: usize) -> Self {
        Self(value)
    }

    #[zoet(From)] // generates `From<Length> for usize`
    fn to_usize(self) -> usize {
        self.0
    }

    #[zoet(AsRef, Borrow, Deref)] // generates all of those.
    fn as_usize(&self) -> &usize {
        &self.0
    }

    #[zoet(Hash)] // see note below about traits with generic functions
    fn hash(&self, state: &mut impl Hasher) {
        self.0.hash(state)
    }

    #[zoet(Add, AddAssign)] // generates `impl Add for Length` and `impl AddAssign for Length`
    fn add_assign(&mut self, rhs: Self) {
        self.0 += rhs.0;
    }

    #[zoet(Ord, PartialOrd, PartialEq)] // you get the idea by now
    fn ord(&self, other: &Self) -> Ordering {
        self.0.cmp(&other.0)
    }
}

let mut v = Length::default();
v += Length(1);
assert_eq!(v + Length(2), Length(3));
v += Length(4);
assert_eq!(v, Length(5));
assert_eq!(Length::from(v), Length(5));

支持的特质

为标准库(corealloc 和/或 std crate)中的大多数特质提供了转换。当前列表如下

  • core::borrow: BorrowBorrowMut
  • core::clone: Clone
  • core::cmp:类型 OrdPartialEqPartialOrd。(Eq 也可以识别,但你会被建议使用 #[derive(Eq)] 替代)。
  • core::convert:类型 AsMutAsRefFromIntoTryFromTryInto
  • core::default:类型 Default
  • core::fmt:类型 BinaryDebugDisplayLowerExpLowerHexOctalPointerUpperExpUpperHexWrite(实现了 write_str)。
  • core::future:类型 FutureIntoFuture
  • core::hash:类型 Hash(实现了 hash)。
  • core::iter:类型 IntoIteratorIterator(实现了 next)。
  • core::ops:类型 DerefDerefMutDropIndexIndexMut,以及所有算术和位运算,以及如 AddAddAssign 之类的赋值变体。
  • core::str:类型 FromStr

默认启用的 alloc 功能还添加了以下内容

  • alloc::borrow:类型 ToOwned
  • alloc::string:类型 ToString

大多数生成的特质通常只包含特质模板代码并将参数转发到你的方法。还有一些有用的额外特殊情况

  • PartialOrd 也可以应用于一个 Ord 形状的功能,在这种情况下,它会用 Some() 包装结果以使其适应。 PartialEq 以相同的方式处理一个 PartialOrdOrd 形状的功能,并在它返回 Ordering::Equal 时返回 true。这允许您一次实现所有这些 #zoet[(Ord, PartialEq, PartialOrd)]。 (如果您生成 PartialEq,您可能还需要 #[derive(Eq)]。)
  • Add 也可以应用于一个 AddAssign 形状的功能,在这种情况下,它会生成一个平凡的实现,它更改其 mut self 并返回它。这适用于所有其他具有 OpAssign 变体的运算符特征。

不受支持的 特征

由于这个宏将单个函数转换为特征,因此需要在函数和特征之间有一个一对一的映射。这意味着需要超过一个函数的特征(例如,Hasher)无法合理地支持。同样,像 FusedIterator 这样的标记特征也不受支持,尽管实现起来很简单,但这实际上是 derive 宏的工作。最后,具有泛型函数的特征,如 FromIteratorExtend,也不受支持,因为这些超出了 zoet 当前的签名解析器的功能。

此外,像 Generator 这样的夜间仅限特征正在避免,因为无法保证 zoet 能够跟踪任何未来的更新。

如果您想添加这些功能并有一些有建设性的建议,请随意在 mooli/zoet-rs GitHub 仓库 上提出问题或 PR。

它生成的代码

会发出一个合适的实现,该实现代理到您的函数,例如

# struct Length(usize);
# impl Length {
#   fn add_assign(&mut self, rhs: Self) { self.0 += rhs.0; }
# }
impl ::core::ops::Add<Self> for Length {
    type Output = Length;
    fn add(mut self, rhs: Length) -> Length {
        <Length>::add_assign(&mut self, rhs);
        self
    }
}
impl ::core::ops::AddAssign<Length> for Length {
    fn add_assign(&mut self, rhs: Length) {
        <Length>::add_assign(self, rhs);
    }
}

您可以使用 cargo-expand 检查实际的展开。

作为一个备注,这个特定的生成代码乍一看可能看起来像是无限递归,但 <Length>::add_assign 明确引用了固有实现中的方法,并且没有歧义。然而,人类读者仍然可能感到困惑——clippy 的 clippy::same_name_method 检查表示同意——您可能希望考虑使用不同的方法名,例如 _add_assignadd_assign_impl,即使这些名字在美学上不那么吸引人。

注意事项

… 由于 Rust 宏的限制

您必须将 #[zoet] 添加到您的结构体的 impl 块中,以便确定其关联函数的自我类型。对于自由函数来说,这显然是不必要的(或可能的),因为它们没有自我类型。

宏在类型检查之前运行,因此实际类型是未知的,宏必须仅使用标记来工作。大多数情况下,只需将标记粘贴到输出中,让编译器自行解决即可,这是 zoet 所做的。如果类型错误,您应该得到一个有用的编译器错误,指出这一点。然而,一些特质需要比简单的粘贴更多,并且为了清晰(和/或消除歧义),zoet 需要 类型有一个特定的 名称

  • PartialOrd:函数应该返回 Option。如果不是,它将像 Ord 一样将其 Some 包装。
  • Future:函数参数必须是 PinPoll 分别。
  • Iterator:函数必须返回 Option
  • TryFromTryIntoFromStr:函数必须返回 Result。如果只提供了一个类型参数,则使用名称 Error 作为错误类型。

只有路径的最后部分会被比较,所以 eyre::Resultcrate::Result::core::result::Result 等等,与裸 Result 一样有效。

这里仅记录此名称检查,以防您正在使用类型别名进行非常奇怪的操作,并且不应影响合理的代码。

… 由于 zoet 设计限制

函数及其/或其固有 impl 的泛型参数都被累积并添加到特质 impl 的泛型参数中,这对于大多数特质来说都是正确的。然而,如果一个特质的功能本身是泛型的,zoet 还不够智能,无法确定哪个泛型参数是用于函数的。作为一个完美的替代方案,请使用 impl Trait 参数。所以,尽管 Hash 定义其单个方法为 fn hash<H: Hasher>(&self, state: &mut H),您的函数需要一个类似于 fn hash(&self, state: &mut impl Hasher) 的签名。

省略的生命周期不能用于被复制到生成的特质的类型,并且需要调整函数以使用命名生命周期。这主要影响像IntoIterator这样的特质,在这种情况下,您将签名从例如fn iter(&self) -> slice::Iter<T>改为fn iter<'a>(&'a self) -> slice::Iter<'a, T>

…因为做得太过分了

虽然这个宏使生成大量核心特质变得容易,但不要疯狂地添加特质,要考虑每个添加的特质,是否有更适合做这项工作的宏,或者是否确实应该添加这个特质。以下是一些避免使用此crate的技巧

  • 上面的例子基于new()生成Default,但由于该函数返回的是默认值0,所以最好使用#derive(Default)并使用该函数来实现new()
  • 同样,它的AddAddAssign修饰的函数是对其字段AddAddAssign特质的简单委托。derive_more crate处理这个问题,并将进一步减少样板代码,在这种情况下,在结构体上简单地添加#[derive(Add, AddAssign)]将替换那些函数。
  • educe允许您在不编写实际样板函数的情况下生成和自定义DebugDefaultHashCloneCopy
  • Borrow不仅是对AsRef的同义词,而且还提供特定的保证,特别是“对于借用和拥有的值,“EqOrdHash必须等效”。如果你的AsRef不提供这些保证,不要写#[zoet(AsRef, Borrow, Deref)]

依赖项

~1.5MB
~36K SLoC