11个版本

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.4 2019年11月6日

#13 in #sweet

Download history 110/week @ 2024-04-29 1/week @ 2024-05-27 278/week @ 2024-07-01 7/week @ 2024-07-08 5/week @ 2024-07-15 199/week @ 2024-07-29 8/week @ 2024-08-05 6/week @ 2024-08-12

213 每月下载量
用于 2 个crate(通过 zoet

MIT 许可证

94KB
1.5K SLoC

为减少实现常见特质的样板代码而添加 #[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
  • 核心::cloneClone
  • 核心::cmpOrdPartialEqPartialOrd。(Eq 被识别,但你会被建议使用 #[derive(Eq)] 代替)。
  • 核心::convertAsMutAsRefFromIntoTryFromTryInto
  • 核心::defaultDefault
  • 核心::fmtBinaryDebugDisplayLowerExpLowerHexOctalPointerUpperExpUpperHexWrite(实现 write_str)。
  • 核心::futureFutureIntoFuture
  • 核心::hashHash(实现 hash)。
  • 核心::iterIntoIteratorIterator(实现 next)。
  • 核心::opsDerefDerefMutDropIndexIndexMut,以及所有算术和位运算,以及赋值变体,如 AddAddAssign
  • 核心::strFromStr

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

  • alloc::borrowToOwned
  • alloc::stringToString

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

  • 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。

它生成的代码

会生成一个合适的 impl,该 impl 将代理到您的函数,如下所示

# 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 明确引用了 inherent impl 中的方法,并且没有歧义。然而,人类读者可能仍然觉得它很困惑——clippy::same_name_method 检查一致——您可能希望考虑使用不同的方法名称,例如 _add_assignadd_assign_impl,即使这些名称在美学上不那么吸引人。

需要注意的地方

… 由于 Rust 宏的限制

您必须在结构的 impl 块中添加 #[zoet],以便确定关联函数的自身类型。显然,对于没有自身类型的自由函数来说,这不是必需的(或者不可能的)。

宏在类型检查之前运行,因此实际类型是未知的,宏只能与标记一起工作。大多数情况下,只需将标记粘贴到输出中,让编译器自行处理即可,这是 zoet 可能做到的。如果类型错误,您应该会得到一个有用的编译器错误,指出这一点。然而,某些特质需要比简单的粘贴更多的工作,为了正常(和/或消除歧义),zoet 需要 类型具有特定的 名称

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

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

这里只记录了名称检查,以防您在使用类型别名做一些非常奇怪的事情,这不应该影响合理的代码。

… 由于 zoet 的设计限制

函数及其固有实现上的泛型参数都只是累积并添加到特质的实现泛型参数中,这对于大多数特质来说都是正确的。然而,如果一个特质的函数本身是泛型的,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存储库处理这个问题,并将进一步减少样板代码的数量,在这种情况下,在结构体上简单地使用#[derive(Add, AddAssign)]就可以替换这些函数。
  • educe允许您在不编写实际样板代码的情况下派生和自定义DebugDefaultHashCloneCopy
  • Borrow不仅仅是一个与AsRef的同义词,它还提供了特定的保证,特别是“对于借用值和拥有值,“EqOrdHash必须等效”。如果你的AsRef不提供这些保证,不要写#[zoet(AsRef, Borrow, Deref)]

依赖

~1.5MB
~36K SLoC