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

我正在尝试创建一个断言库,用于在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,
}
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的情况非常有效nd
be
,但即使是你也不想随意跳过它们。(我意识到我没有将其列为上述要求)好主意!是的,有了宏,你可以做任何事情。实际上,我试图避免在这个库中使用宏,因为这是主要的原因,以便获得良好的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")
            }
        }
    }
}