Rust 如何创建一个使用流畅链接语法而不需要括号的类型?
我正在尝试创建一个断言库,用于在Rust中进行测试。目前,我有如下声明:Rust 如何创建一个使用流畅链接语法而不需要括号的类型?,rust,fluent,assertion,Rust,Fluent,Assertion,我正在尝试创建一个断言库,用于在Rust中进行测试。目前,我有如下声明: expect(value).to().be().equal_to(4); 将填充to和be函数上的参数放到下面的函数中会非常好: expect(value).to.be.equal_to(4); 我认为这需要to和be成为expect(Expectation)返回的struct上的字段。目前看起来是这样的: struct Expectation<V: Debug> { value: V, } st
expect(value).to().be().equal_to(4);
将填充to
和be
函数上的参数放到下面的函数中会非常好:
expect(value).to.be.equal_to(4);
我认为这需要to
和be
成为expect
(Expectation
)返回的struct上的字段。目前看起来是这样的:
struct Expectation<V: Debug> {
value: V,
}
struct Expectation<V: Debug> {
value: V,
to: Box<Expectation<V>>,
be: Box<Expectation<V>>,
}
struct期望值{
值:V,
}
有没有可能这样做:
struct Expectation<V: Debug> {
value: V,
}
struct Expectation<V: Debug> {
value: V,
to: Box<Expectation<V>>,
be: Box<Expectation<V>>,
}
struct期望值{
值:V,
致:盒子,
是:盒子,
}
其中to
和be
指向它们所在的结构
我试过了,但这是一个复杂的构造。我甚至不确定移动对象是否安全(可能可以通过Pin
?)来防止)
我正在寻找任何允许上述
expect(value).to.be
语法的解决方案。我已经成功地使用板条箱惰性地生成了to
和be
:
为了设计自定义语法,我只使用宏:
macro\u规则!期待{
($subject:expr,to,$($attr:tt)*)=>{
期望!($subject,$($attr)*)
};
($subject:expr,be,$($attr:tt)*)=>{
期望!($subject,$($attr)*)
};
($subject:expr,等于$object:expr)=>{
assert_eq!($subject,$object)
};
}
期待!(1,to,be,等于1);
仅仅为了获得特定语法而部署框和自引用结构是过分的
我正在寻找任何允许上述expect(value).to.be
语法的解决方案
那就简单点吧:
fn main() {
expect(4).to.be.equal_to(3);
}
fn expect<T>(actual: T) -> To<T> {
let be = Be {
be: Expectation(actual),
};
To { to: be }
}
struct To<T> {
pub to: Be<T>,
}
struct Be<T> {
pub be: Expectation<T>,
}
struct Expectation<T>(T);
impl<T> Expectation<T> {
fn equal_to<U>(&self, expected: U)
where
U: PartialEq<T>,
{
if expected != self.0 {
panic!("Report error")
}
}
}
有些宏可以减少重复,但我太懒了,无法实际演示;-)
在罗马的时候。。。
在设计测试断言库时,我会尽量发挥Rust的优势。对我来说,这意味着使用traits允许人们轻松地添加自定义断言
use crate::testlib::prelude::*;
fn main() {
expect(4).to(be.equal_to(3));
expect(4).to(equal_to(3));
}
mod testlib {
// Shorthand variants that will always be imported.
// Minimize what's in here to avoid name collisions
pub mod prelude {
use super::*;
pub fn expect<A>(actual: A) -> Expectation<A> {
Expectation::new(actual)
}
#[allow(non_upper_case_globals)]
pub static be: Be = Be;
pub fn equal_to<E>(expected: E) -> EqualTo<E> {
EqualTo::new(expected)
}
}
// All the meat of the implementation. Can be divided up nicely.
pub trait Assertion<A> {
fn assert(&self, actual: &A);
}
pub struct Expectation<A>(A);
impl<A> Expectation<A> {
pub fn new(actual: A) -> Self {
Expectation(actual)
}
pub fn to(&self, a: impl Assertion<A>) {
a.assert(&self.0)
}
}
pub struct Be;
impl Be {
pub fn equal_to<E>(&self, expected: E) -> EqualTo<E> {
EqualTo::new(expected)
}
}
pub struct EqualTo<E>(E);
impl<E> EqualTo<E> {
pub fn new(expected: E) -> Self {
EqualTo(expected)
}
}
impl<A, E> Assertion<A> for EqualTo<E>
where
A: PartialEq<E>,
{
fn assert(&self, actual: &A) {
if *actual != self.0 {
panic!("report an error")
}
}
}
}
使用板条箱::testlib::序言::*;
fn main(){
期望(4)等于(3);
期望(4)到(等于(3));
}
mod testlib{
//将始终导入的速记变体。
//最小化此处的内容以避免名称冲突
酒吧前奏曲{
使用超级::*;
pub fn expect(实际值:A)->expect{
期望值:新的(实际的)
}
#[允许(非大写字母)]
pub静态be:be=be;
发布fn等于(预期值:E)->EqualTo{
EqualTo::新建(预期)
}
}
//实现的所有内容都可以很好地划分。
发布特征断言{
fn断言(&self,实际:&A);
}
发布结构预期(A);
隐含期望{
新发布(实际版本:A)->Self{
期望值(实际值)
}
发布fn到(&self,a:impl断言){
a、 断言(&self.0)
}
}
pub-struct-Be;
暗示{
发布fn等于(&self,预期为:E)->EqualTo{
EqualTo::新建(预期)
}
}
公共结构平等(E);
请求平等{
pub fn new(预期:E)->Self{
EqualTo(预期)
}
}
EqualTo的impl断言
哪里
A:PartialEq,
{
fn断言(&self,实际:&A){
如果*实际!=self.0{
惊慌失措!(“报告错误”)
}
}
}
}
接下来我将研究以下步骤:
- 断言可能应该向某些传入的结构报告失败,而不是报告死机
- 将
和/或添加到_not
负匹配器not\u添加到
- 添加断言的组合
expect(value)更符合人体工程学或更友好。等于(4)
或甚至expect(value,4)
这只是一个(仓促、小)示例-这里不是真的讨论它的人体工程学(尽管我会说它确实会对更大的断言产生影响,特别是对于许多组合在一起的断言——请参见hamcrest或assertj以了解Java中的类似内容)。也是一个很好的脑筋急转弯/帮助我学习生锈。我喜欢语法,但只在正常的语言中使用。编写符合生态系统美学的代码,而不仅仅是你以前使用过的代码。更简而言之,在罗马时,要像罗马人那样做。另请参见:啊,这对你总是必须使用到a的情况非常有效ndbe
,但即使是你也不想随意跳过它们。(我意识到我没有将其列为上述要求)好主意!是的,有了宏,你可以做任何事情。实际上,我试图避免在这个库中使用宏,因为这是主要的原因,以便获得良好的IDE自动完成/支持以及更好的可扩展性和可组合性。
use crate::testlib::prelude::*;
fn main() {
expect(4).to(be.equal_to(3));
expect(4).to(equal_to(3));
}
mod testlib {
// Shorthand variants that will always be imported.
// Minimize what's in here to avoid name collisions
pub mod prelude {
use super::*;
pub fn expect<A>(actual: A) -> Expectation<A> {
Expectation::new(actual)
}
#[allow(non_upper_case_globals)]
pub static be: Be = Be;
pub fn equal_to<E>(expected: E) -> EqualTo<E> {
EqualTo::new(expected)
}
}
// All the meat of the implementation. Can be divided up nicely.
pub trait Assertion<A> {
fn assert(&self, actual: &A);
}
pub struct Expectation<A>(A);
impl<A> Expectation<A> {
pub fn new(actual: A) -> Self {
Expectation(actual)
}
pub fn to(&self, a: impl Assertion<A>) {
a.assert(&self.0)
}
}
pub struct Be;
impl Be {
pub fn equal_to<E>(&self, expected: E) -> EqualTo<E> {
EqualTo::new(expected)
}
}
pub struct EqualTo<E>(E);
impl<E> EqualTo<E> {
pub fn new(expected: E) -> Self {
EqualTo(expected)
}
}
impl<A, E> Assertion<A> for EqualTo<E>
where
A: PartialEq<E>,
{
fn assert(&self, actual: &A) {
if *actual != self.0 {
panic!("report an error")
}
}
}
}