1 个不稳定版本
0.0.0 | 2023年11月6日 |
---|
#73 在 #newtype
4KB
63 行
目录
正在进行中:此crate正在开发中,文档包含一些 计划 功能。
新类型惯用法
新类型惯用法 是Rust中一种强大的模式,用于通过类型系统区分相同基础类型的不同用途。
遗憾的是,实现新类型需要大量的样板代码。此crate提供两个宏,方便地为您生成这些样板代码。
您可以对生成的代码有完全的控制权,并且可以根据需要添加自定义实现。这些宏旨在以最方便的方式使用,以便在大多数情况下无需修改即可使用,同时在需要时可以进一步完善。
示例
最简单的情况是,您可以像这样使用它
use newtypedecl::newtype;
// This will generate a basic API for `Example` and implement some common traits.
#[newtype]
pub struct Example(String);
// a 'new' fn is generated
let example = Example::new("foo");
// AsRef is automatically implemented for all types the inner type implements AsRef for
let reference: &[u8] = example.as_ref();
assert_eq!(reference, b"foo");
当使用属性宏语法会因为添加了许多自定义控制定义而变得杂乱无章,或者如果想要批量定义更多的新的类型结构时,可以提供一个类似函数的宏语法,其中控制块位于结构体之后
use std::ops::{Deref, DerefMut};
use newtypedecl::newtype;
#[newtype(!fn new, impl Deref, impl DerefMut)]
pub struct Example(String);
#[newtype]
pub struct Another(i32);
// and so on...
等同于
use std::ops::{Deref, DerefMut};
use newtypedecl::newtypes;
newtypes! {
pub struct Example(String) {
!fn new;
impl Deref;
impl DerefMut;
}
pub struct Another(i32);
// and so on...
}
新类型结构
为了语法清晰,新类型总是作为元组结构实现,其中包裹的数据成员位于第一个位置。可以跟随任意数量的零大小类型成员作为标记,只要它们可以简单地构造。在本文档中,包裹的数据/类型将进一步被称作内部/Inner。
与任何结构声明一样,该宏支持属性、生命周期和泛型。
强制实施新类型惯用法
默认情况下,会自动添加 #[repr(transparent)]
到生成的结构体中。这确保了结构体只有一个非零大小的数据成员。仍然允许有任意数量的标记成员。
当指定了其他任何 repr
属性时,宏将不会添加 #[repr(transparent)]
。使用 [repr(Rust)]
来抑制 #[repr(transparent)]
。
控制块
虽然新类型宏可以自己完成很多工作,但它们需要某种方法来控制代码的生成方式。这是通过包含控制指令和简写Rust形式的特殊语法块来实现的。
- 在
#[newtype]
属性宏中,这通过括号中的逗号分隔列表传递,例如:#[newtype(!fn new, impl Deref)]
。 - 在类似于 'newtypes!{}' 的函数宏中,控制指令在结构定义之后的大括号内的块中传递,没有以大括号结束的实体需要在该处用分号分隔。
控制块是可选的。您始终可以添加新类型结构的实现块和在正常Rust代码中实现特质的实现。对于任何非样板代码,显式实现确实是推荐的方式。
此外,生成的代码直接从新类型结构定义中获取生命周期、泛型和约束。如果需要细化这些,则必须显式实现。
控制指令
我们控制将生成哪些方法和成员函数以及为新类型实现哪些特质。
控制指令可以禁用默认生成的那些,或定义/启用默认未启用的那些。
要禁用某个指令,在其指定前加一个感叹号 !fn $name
将抑制自动生成一个 $name
成员函数(尽管可以定义一个自定义的)。 !impl $name
将抑制自动实现特质 $name
。
有两种特殊形式: !default fn
和 !default impl
将抑制所有默认函数和特质的实现。
新类型宏了解并特别实现的特性和函数将在末尾描述。
缩略定义
从这里开始就有趣了。新类型生成器了解很多函数和特质,可以自己生成必要的代码。通常只需要简写定义,这些定义会自动完成。
特质
可以使用正常的Rust语法形式来实现特质
impl<'lifetimes, Generics> TraitName for Newtype
where
Constraints
{
fn trait_fn() { /*....*/ }
}
但实际上,在许多情况下可以省略这些,因为我们知道新类型和我们要实现的特质
- 可以从新的类型结构体和特性中计算泛型的生命周期。
- 由于我们知道我们想要为给定的类型实现它,所以
for NewType
部分是多余的。 - 约束可以从特性和新类型定义中计算得出。
- 新类型宏了解常见的特性和如何为它们实现特性函数,以提供正确的'新类型'语义。
注意:尚未全部实现
impl TraitName
对于新类型宏所了解的标准特性来说,这已经足够了。通常,它将创建一个泛型实现,使其与包装的内类型的相应特性实现兼容。例如,AsRef
不会引用Inner类型,而是引用内类型实现AsRef
的所有类型。impl TraitName<type>
当需要更具体的实现时,可以对特定类型进行特殊化。impl<T> TraitName<T> whereConstraint
添加额外的约束。注意,新类型中的泛型不需要在这里传递,新类型宏会合并它们。impl UnknownTrait { fn traitfn(){...} }
对于新类型宏未知特性的特性,需要一个特性函数的块。新类型的泛型、'for Newtype'和where子句是不需要的。
成员函数
与特性不同,新类型宏试图为未知函数生成代码。这是通过简单地使用相同的函数调用内值来完成的。当提供的缩写不充分时,这可能会导致后续的编译器错误。
fn name
- 对于已知函数,将生成实现。
- 对于未知函数,这翻译为
fn name(&self) {self.0.name()}
fn name ->Type
- 对于未知函数,这翻译为
fn name(&self) -> Type {self.0.name()}
- 对于未知函数,这翻译为
fn name(&mut self) ->Type
- 对于未知函数,这翻译为
fn name(&mut self) -> Type {self.0.name()}
- 对于未知函数,这翻译为
fn name(&xx)
与fn name(xx)
已知函数根据类型是通过值传递还是通过引用传递(或者通过 dyn/impl 某些受支持的特质传递)可能会有一些变体。
如上所述,当未定义未知函数的主体时,将生成将调用转发到 Inner 类型的主体。这默认为 (&self)
作为参数且没有返回值,当期望转发的函数返回某些内容时,必须显式给出返回类型。这种生成的转发函数将 不会 从新类型结构定义中继承 pub
可见性。
use newtypedecl::newtypes;
newtypes! {
struct Forwarding(String) {
// calls String::len()
pub fn len -> usize;
// We need to specify `&mut self` here since `&self` is the default
pub fn clear(&mut self);
}
}
let mut n = Forwarding::new("foobar");
assert_eq!(n.len(), 6);
n.clear();
assert_eq!(n.len(), 0);
已知函数和特质
函数
fn new
, fn new_*
这完成了所有类似 'new' 的函数。
- 将生成文档注释/属性。
- 'pub' 可见性从新类型结构继承。
- 参数默认为
impl Into<Inner>
。当给出参数时,它们可以是空的,Inner
,&Inner
,impl Into<Inner>
,dyn Into<Inner>
。 - 返回类型默认为
Self
。支持其他包装器,但应可以使用Other::new(inner)
构建。这允许完成像fn new_arc -> Arc<Self>
这样的函数,这将扩展为fn new_arc(inner: impl Into<Inner>) -> Arc<Self> {Arc::new(inner.into())}
。 - 当没有给出主体时,它将使用上述收集的信息来完成。
fn new(inner: impl Into<Inner>) -> Self
默认生成。要抑制此行为,请在控制块中添加 !fn new
或细化缩写。
特质
AsRef
和 AsMut
这些特质默认实现,要抑制它们,请在控制块中添加 !impl AsRef
和/或 !impl AsMut
。
默认实现为任何新类型内部类型实现了 AsRef
/AsMut
。
impl<T> AsXXX<T> for Inner
where
Inner: AsXXX<T> {...}
Borrow
和 BorrowMut
这些特质默认不实现,要启用它们,请在控制块中添加 impl Borrow
和/或 impl BorrowMut
。
默认实现与上述 AsRef
/AsMut
实现类似。
use std::borrow::{Borrow, BorrowMut};
use newtypedecl::newtype;
#[newtype(impl Borrow, impl BorrowMut)]
pub struct Example(i32);
let mut s = Example::new(1234);
*s.borrow_mut() = 3456;
let r: &i32 = s.borrow();
assert_eq!(*r, 3456);
Deref
和 DerefMut
这些特质默认不实现,要启用它们,请在控制块中添加 impl Deref
和/或 impl DerefMut
。 DerefMut
需要 Deref
。
实现这些特质需要一些思考,因为新类型习语旨在隐藏底层类型,并使用类型系统来强制每个新类型被独特对待。引用反转反过来使类型看起来像其他类型,在许多情况下,使用新类型时这通常是不希望的。
默认实现为任何内部类型实现了 Deref
的类型定义 Deref
。
impl<T> Deref for Inner
where
Inner: Deref<Target = T>,
{
type Target = <Inner as Deref>::Target;
...
}
DerefMut
依赖于 Deref
,并有一个相当简单的实现,只是返回 &mut self.0
。
use newtypedecl::newtype;
use std::ops::Deref;
#[newtype(impl Deref)]
struct DerefTest(String);
let n = DerefTest::new("foobar");
assert_eq!(&*n, "foobar");
From
类似于 fn new
,From
特性为所有内部类型实现了 From
的类型实现。此特性默认实现,要禁用它,请在控制块中添加 !impl From
。当新类型泛型如 struct NewType<T>(T)
时,需要自定义实现或禁用 From
,因为默认实现将与 stdlib 的 impl<T> From<T> for T
空白实现冲突。
use newtypedecl::newtype;
#[newtype]
struct FromTest(String);
let n = FromTest::from("foobar");
assert_eq!(n.0, "foobar");
限制
From
特性不适用于泛型内部类型。在将来,这将检测并禁用/修复默认的From
实现。- 特性匹配基于标识符名称,给出完整路径会失败,'impl std::ops::Deref' 不可识别,这将在未来版本中修复。
- 尚未处理封装的内部类型,如
struct BoxedInner(Box<Inner>)
,这是一个错误,将尽快修复。
计划:函数
尚未实现,请查看新版本。发送 PR 或打开工单报告您缺少的内容。
-
fn inner
,fn inner_mut
返回内部类型的引用 -
fn into_inner
消耗自身,返回内部类型 -
fn invariant_check(&内部) -> bool {/*必需的实现*/}
验证内部类型的值。当存在时,其他函数/特性将使用它们。还定义了一些实用函数fn assert_invariant(&Inner)
,fn invariant(Inner) -> Inner
在不变量失败时引发恐慌。fn try_invariant(内部) -> Result<Inner,newtypedecl::InvariantError>
在不变量失败时返回错误
为了维持不变量保证,必须满足某些条件
inner_mut
必须是 unsafe,因为它可以用来破坏不变量。这会自动添加。- 当使用不变量时,内部类型不得为 pub。这将导致编译错误。
- 禁用返回可变引用的默认特性。
- 程序员负责在任何自定义函数和特性实现中强制执行不变量。
-
fn invariant_tryfix(&mut内部) -> bool {/*必需的实现*/}
类似于invariant_check()
,但会尝试修正内部变量以符合不变性。当定义时,只要可用可变内部变量就会使用。
计划:特质
尚未实现,请查看新版本。发送 PR 或打开工单报告您缺少的内容。
待办事项:测试和记录与 derive_more 的兼容性
TryFrom
FromStr
ToOwned