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:
struct
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?
No, there are no named/keyword parameters in Rust. They have been discussed for a long time, but there are no concrete plans to add them. If you have many parameters in a function, consider passing a struct, the builder pattern, etc.
We define a function in Rust by entering fn followed by a function name and a set of parentheses. The curly brackets tell the compiler where the function body begins and ends. We can call any function we've defined by entering its name followed by a set of parentheses.
What are keyword arguments? Keyword arguments are a feature in Ruby 2.0 and higher. They're an alternative to positional arguments, and are really similar (conceptually) to passing a hash to a function, but with better and more explicit errors.
Disclaimer: I advise against using this solution, because the errors reported are horrid. The cleanest solution, codewise, is most probably the builder pattern.
With that out of the way... I whipped together a proof-of-concept demonstrating operator abuse.
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).
It looks like:
// 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);
}
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).
The normal definition of 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)
}
}
Requires some augmentation:
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 };
And yes, this does mean that you can augment a function/method defined in a 3rd party library.
This is supported by:
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)
}
}
Note that this does NOT address:
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.
The result of calling p.button("Hello");
is:
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`
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With