rust - 如何在 Rust 中最好地 *fake* 关键字样式函数参数?

标签 rust parameter-passing keyword-argument

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

对于提供关键字参数的语言,类似这样的事情很常见:

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

我见过使用构建器模式处理的,例如:

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

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


但是使用 impl Default 的结构初始化和 Option<..> Rust 中的成员可用于获得非常接近于实际上类似于编写关键字参数的东西,例如:

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()
});

这可行,但在尝试用作关键字参数的上下文中有一些缺点:

  • 必须在参数前加上 struct 的名称
    (还需要将其显式包含在命名空间中会增加一些开销)。
  • 投入 Some(..)围绕每个可选参数都很烦人/冗长。
  • .. Default::default()每次使用结束时都有点乏味。

有没有办法减少其中的一些问题,(例如使用宏) 以替代关键字访问使这项工作更容易?

最佳答案

免责声明:我建议不要使用此解决方案,因为报告的错误非常可怕。从代码角度来看,最简洁的解决方案很可能是构建器模式。


除此之外...我拼凑了一个证明运算符(operator)滥用的概念验证。

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

另一方面,它确实需要导入大量符号(要使用的每个名称)。

看起来像:

//  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)。

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 方库中定义的函数/方法。

这得到了支持:

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 这样的解决方案是可能的:

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 }
    }
}

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

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));
}

当有多个参数时,额外的一组括号是必要的。

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

调用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 - 如何在 Rust 中最好地 *fake* 关键字样式函数参数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41629819/

相关文章:

rust - 如何配置Clion,以便在Rust代码中显示嵌体类型提示?

rust - 引用内部闭包的迭代器的智能构造函数

python - 在 Windows 中关闭 Python 中的主程序时,通过子进程传递参数并终止子进程

types - Julia:是否可以将参数字典传递给函数?

Python:kwargs.pop() 和 kwargs.get() 之间的区别

c++ - 无法使 rustc 使用 simd 指令进行包含范围循环

rust - 了解 Rust 库的依赖关系

jsf - 如何在 JSF 中将对象从一个页面传递到另一个页面而不编写转换器

java - JSF 2.0 向同一页面传递参数

python - 如何在python中将所有参数转换为字典