如何在Rust中最好地使用* fake *关键字样式函数参数? [英] How to best *fake* keyword style function arguments in Rust?

查看:98
本文介绍了如何在Rust中最好地使用* fake *关键字样式函数参数?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有兴趣在功能上类似于Rust中的关键字参数,目前尚不支持它们.

I'm interested to have something functionally similar to keyword arguments in Rust, where they're currently not supported.

对于提供关键字参数的语言,这种情况很常见:

For languages that provide keyword argument, something like this is common:

panel.button(label="Some Button")
panel.button(label="Test", align=Center, icon=CIRCLE)

我已经看到了使用builder-pattern处理的问题,例如:

I've seen this handled using the builder-pattern, eg:

ui::Button::new().label("Some Button").build(panel)
ui::Button::new().label("Test").align(Center).icon(CIRCLE).build(panel)

这很好,但与Python中的关键字参数相比有时有些尴尬.

Which is fine but at times a little awkward compared with keyword arguments in Python.

但是,在Rust中将结构初始化与impl DefaultOption<..>成员一起使用可以得到非常接近的东西,这实际上类似于编写关键字参数,例如:

However using struct initialization with impl Default and Option<..> members in Rust could be used to get something very close to something which is in practice similar to writing keyword arguments, eg:

ui::button(ButtonArgs { label: "Some Button".to_string(), .. Default::default() } );

ui::button(ButtonArgs {
    label: "Test".to_string(),
    align: Some(Center),
    icon: Some(Circle),
    .. Default::default()
});

这可行,但在尝试用作关键字args的情况下存在一些缺点:

This works, but has some down-sides in the context of attempting to use as keyword args:

  • 必须在参数前面加上struct
    的名称(还需要在名称空间中明确包含它,这会增加一些开销).
  • 在每个可选参数周围加上Some(..)令人讨厌/冗长.
  • 每次使用结束时,
  • .. Default::default()都有些乏味.
  • Having to prefix the arguments with the name of the struct
    (also needing to explicitly include it in the namespace adds some overhead).
  • Putting Some(..) around every optional argument is annoying/verbose.
  • .. Default::default() at the end of every use is a little tedious.

是否有办法减少其中的某些问题(例如使用宏)(例如使用宏),以使其更轻松地代替关键字访问?

Are there ways to reduce some of these issues, (using macros for example) to make this work more easily as a replacement for keyword access?

推荐答案

免责声明:我建议不要使用此解决方案,因为报告的错误是可怕的.用代码方式,最干净的解决方案很可能是构建器模式.

顺带一提,我整理了一个概念证明,证明了操作员的滥用.

With that out of the way... I whipped together a proof-of-concept demonstrating operator abuse.

与使用struct语法传递参数或使用构建器相比,它的主要优势在于,它允许跨使用相同参数的不同集合的函数重用.

Its main advantage over using struct syntax to pass arguments, or using a builder, is that it allows reuse across functions taking different sets of the same parameters.

另一方面,它的确遭受了必须导入大量符号(每个要使用的名称)的困扰.

On the other hand, it does suffer from having to import a whole lot of symbols (each name to be used).

它看起来像:

//  Rust doesn't allow overloading `=`, so I picked `<<`.
fn main() {
    let p = Panel;
    p.button(LABEL << "Hello", ALIGNMENT << Alignment::Center);

    p.button(LABEL << "Hello", Alignment::Left);
    p.button(Label::new("Hello"), Alignment::Left);
}

请注意,该名称实际上是可选的,它仅用作参数本身的构建器,但是如果您已经有该参数,则可以避开该名称.这也意味着为显而易见的"参数(此处为Alignment)创建名称可能不值得.

Note that the name is really optional, it merely servers as a builder for the argument itself, but if you already have the argument it can be eschewed. This also means that it's probably not worth creating a name for "obvious" parameters (Alignment here).

button的常规定义:

#[derive(Debug)]
struct Label(&'static str);

#[derive(Debug)]
enum Alignment { Left, Center, Right }

struct Panel;

impl Panel {
    fn button(&self, label: Label, align: Alignment) {
        println!("{:?} {:?}", label, align)
    }
}

需要一些补充:

impl Carrier for Label {
    type Item = &'static str;
    fn new(item: &'static str) -> Self { Label(item) }
}

impl Carrier for Alignment {
    type Item = Alignment;
    fn new(item: Alignment) -> Self { item }
}

const LABEL: &'static Argument<Label> = &Argument { _marker: PhantomData };
const ALIGNMENT: &'static Argument<Alignment> = &Argument { _marker: PhantomData };

是的,这确实意味着您可以扩展在第3方库中定义的功能/方法.

And yes, this does mean that you can augment a function/method defined in a 3rd party library.

此功能受以下支持:

trait Carrier {
    type Item;
    fn new(item: Self::Item) -> Self;
}

struct Argument<C: Carrier> {
    _marker: PhantomData<*const C>,
}

impl<C: Carrier> Argument<C> {
    fn create<I>(&self, item: I) -> C
        where I: Into<<C as Carrier>::Item>
    {
        <C as Carrier>::new(item.into())
    }
}

impl<R, C> std::ops::Shl<R> for &'static Argument<C>
    where R: Into<<C as Carrier>::Item>,
          C: Carrier
{
    type Output = C;
    fn shl(self, rhs: R) -> C {
        self.create(rhs)
    }
}

请注意,这不能解决:

  • 无序传递参数
  • 可选参数

如果用户有足够的耐心来枚举可选参数的所有组合,则可以使用@ljedrz这样的解决方案:

If a user is patient enough to enumerate all combinations of optional parameters, a solution like @ljedrz is possible:

struct ButtonArgs {
    label: Label,
    align: Alignment,
    icon: Icon,
}

impl From<Label> for ButtonArgs {
    fn from(t: Label) -> ButtonArgs {
        ButtonArgs { label: t, align: Alignment::Center, icon: Icon::Circle }
    }
}

impl From<(Label, Alignment)> for ButtonArgs {
    fn from(t: (Label, Alignment)) -> ButtonArgs {
        ButtonArgs { label: t.0, align: t.1, icon: Icon::Circle }
    }
}

impl From<(Label, Icon)> for ButtonArgs {
    fn from(t: (Label, Icon)) -> ButtonArgs {
        ButtonArgs { label: t.0, align: Alignment::Center, icon: t.1 }
    }
}

impl From<(Label, Alignment, Icon)> for ButtonArgs {
    fn from(t: (Label, Alignment, Icon)) -> ButtonArgs {
        ButtonArgs { label: t.0, align: t.1, icon: t.2 }
    }
}

impl From<(Label, Icon, Alignment)> for ButtonArgs {
    fn from(t: (Label, Icon, Alignment)) -> ButtonArgs {
        ButtonArgs { label: t.0, align: t.2, icon: t.1 }
    }
}

然后将允许以下所有组合:

will then allow all of the following combinations:

fn main() {
    let p = Panel;
    p.button( LABEL << "Hello" );
    p.button((LABEL << "Hello"));
    p.button((LABEL << "Hello", ALIGNMENT << Alignment::Left));
    p.button((LABEL << "Hello", ICON << Icon::Circle));
    p.button((LABEL << "Hello", ALIGNMENT << Alignment::Left, ICON << Icon::Circle));
    p.button((LABEL << "Hello", ICON << Icon::Circle, ALIGNMENT << Alignment::Left));

    p.button(Label::new("Hello"));
    p.button((LABEL << "Hello", Alignment::Left, Icon::Circle));
}

如果有多个参数,则需要额外的一组括号.

The extra set of parentheses is necessary when there is more than one argument.

但是有很大的缺点:使用错误的参数集会降低用户体验.

However there is big downside: the user experience is degraded when using the wrong set of parameters.

调用p.button("Hello");的结果是:

error[E0277]: the trait bound `ButtonArgs: std::convert::From<&str>` is not satisfied    --> <anon>:124:7
    | 124 |     p.button("Hello");
    |       ^^^^^^ the trait `std::convert::From<&str>` is not implemented for `ButtonArgs`
    |
    = help: the following implementations were found:
    = help:   <ButtonArgs as std::convert::From<Label>>
    = help:   <ButtonArgs as std::convert::From<(Label, Alignment)>>
    = help:   <ButtonArgs as std::convert::From<(Label, Icon)>>
    = help:   <ButtonArgs as std::convert::From<(Label, Alignment, Icon)>>
    = help: and 1 others
    = note: required because of the requirements on the impl of `std::convert::Into<ButtonArgs>` for `&str`

这篇关于如何在Rust中最好地使用* fake *关键字样式函数参数?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆