What is the best way to emulate "named arguments"?

A lot of RFCs is published but until now, the "named arguments" still unavailable.
Thus, I want to know if there is some method for emulate the "named arguments".

I firstly found emulation here, which suggest using Struct::new().arg1(val1).arg2(val2)... to achieve what we are doing with "named arguments".

But, things may be difficult since what we may not want to change the arguments after the `new()` is called...
let mut database=Database::new().capacity(10000000).row(13);

The difficult is, we should return a Database with all the internal value, Database::new() Database::new().capacity(10000000), Database::new().capacity(10000000).row(13) and many possible solution like Database::new().row(2).capacity(10000000).row(13).

which means we should implement capacity and row for all Database, which may be buggy.


Inspired by such grammar, I tried a pseudo marco:

let target=!Config::new().arg1(val1).arg2(val2)...
which is implemented by hacking `std::ops::Not`
#[derive(Debug)]
struct Cfg{
    a:i32,
    b:i32
}
#[derive(Debug)]
struct Target{
    config:Cfg,
    result:i32
}
impl Target{
  fn new_with_config(config:Cfg)->Self{
    Self{result:config.a+config.b,config:config}
  }
}
use std::ops::Not;
impl Not for Cfg{
    type Output=Target;
    fn not(self)->Self::Output{
        Self::Output::new_with_config(self)
    }
}
impl Cfg{
    fn new()->Self{Self{a:0,b:0}}
    fn a(mut self,a:i32)->Self{self.a=a;self}
    fn b(mut self,b:i32)->Self{self.b=b;self}
}
fn main(){
    let build=!Cfg::new().a(5).b(3);
    println!("{:?}",build);
}

this try avoid implement runtime changing at the cost of:

  • adding a !
  • should manually hack std::ops::{something}
  • while hacking, I must type both type Output=Target and fn not(self)->Self::Output, which is kind of verbose

I tried to avoid the !, but found it is very difficult since there is no Assign trait. and we could not using other *Assign since let target+=Cfg::new(); is illegal.

what's more, if what we want is a function(e.g., PRNG that generate bools), it is quite hard to tell the meaning among,

  PRNG_conf::new().ntype(PRNG::type::Bool).probability(0.1)
 !PRNG_conf::new().ntype(PRNG::type::Bool).probability(0.1)
!!PRNG_conf::new().ntype(PRNG::type::Bool).probability(0.1)

Is there a better way for "named arguments"?

1 Like

Existing implementations add a method like .build() or .finish(), e.g.:

or alternatively keep returning Self that is always in a usable state, so that it can be passed to a function at any stage.

I made a little macro in Short enum variant syntax in some cases - #9 by scottmcm - Rust Internals that allows the call site to look like this:

fn main() {
    do_the_thing(s! { level: 4, name: "hello".into() });
}

Demo: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=424523aadf95bcd49a629f5e97531ad0

2 Likes

Additionally, if you're comfortable writing this, the macro isn't even needed:

fn main() {
    do_the_thing(ThingOptions { level: 4, name: "hello".into(), ..Default::default() });
}

I think it is important to mention here @oooutlk's crate:

as well as @matt1992's:

  • This one is more general / powerful since it exposes a full set of traits to abstract over having certain fields, but may be a bit more complex than the previous one because of that

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.