Search code examples
rustparameter-passingkeyword-argument

How to best *fake* keyword style function arguments in 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)

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)

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


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

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

  • 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?


Solution

  • I think macros are the best solution to this problem. You can use the builder API and provide an easier macro-based sugar for those who dislike the builder pattern. Using the example in the question:

    pub enum Shape { Circle }
    pub enum Alignment { Center }
    pub struct Button();
    
    impl Button {
        pub fn new() -> Button {Button()}
        pub fn label(self, x: &str) -> Button { self }
        pub fn align(self, x: Alignment) -> Button { self }
        pub fn icon(self, x: Shape) -> Button { self }
    }
    
    
    macro_rules! button {
        ( $($i:ident = $e:expr),* ) => { 
            {
                let btn = Button::new();
                $(
                    btn = btn.$i($e);
                )*
                btn
            }
        };
    }
    
    fn main() {
        let my_button = button!(label="hello", align=Alignment::Center, icon=Shape::Circle);
        // Equivalent to
        // let my_button = Button::new().label("hello").align(Alignment::Center).icon(Shape::Circle);
    }