Optional Function Parameters

I've seen tons of information about functions taking Option<T> types as parameters, but what I want is to have a function that may take zero, one, or several parameters and handle each case correctly. Is this impossible? I'm thinking something like the following, but I'm told by the compiler I can't have multiple definitions.


struct Foo {
    bar: String,
    baz: i32,
    quux: bool
}

impl Default for Foo {
    fn default() -> Self {
        Foo {
            bar: "".to_string(),
            baz: 0,
            quux: false
        }
    }
}

impl Foo {
    fn new() -> Self {
        Foo::Default::default()
    }

    fn new(x: String) -> Self {
        Foo {
            bar: x,
            ..Default::default()
        }
    }
    
    fn new(x: i32) -> Self {
        Foo {
            baz: x,
            ..Default::default()
        }
    }
    
    fn new(x: bool) -> Self {
        Foo {
            quux: x,
            ..Default::default()
        }
    }
}

https://github.com/rust-unofficial/patterns/blob/master/patterns/builder.md

I’ve found this does a great job succinctly explaining why it’s impossible in rust (lacks overloading) and shows one of the alternatives.

There’s also this crate which May be able to do the work for you.

Bummer.
Is it possible to do with some sort of macro?
It seems at least for simple cases matching on type using Any could be an avenue:

You could use traits to simulate finction overloading.

You can use the Fn* traits, if you're feeling adventurous:

Playground. This lets you go as far as traditional function overloading. The best example I can think of is C# overloading, where the number of arguments, their order, or their types must change, while only one return type can be present. It also has all three Fn* traits implemented, so you can pass it in as either a FnOnce, Fn or FnMut.

Give me a moment and I'll make a macro to automatically generate these.


Edit: Only downside I just realized, is that you can't use visibility modifiers, as though you can't hide trait implementations.

1 Like

With traits,

pub trait<T> With<T> {
    fn with(value: T) -> Self;
}

struct Foo {
    bar: String,
    baz: i32,
    quux: bool
}

impl Default for Foo {
    fn default() -> Self {
        Foo {
            bar: "".to_string(),
            baz: 0,
            quux: false
        }
    }
}

impl Foo {
    fn new() -> Self {
        Foo::Default::default()
    }
}

impl With<String> for Foo {
    fn with(x: String) -> Self {
        Foo {
            bar: x,
            ..Default::default()
        }
    }
}

impl With<i32> for Foo {
    fn with(x: i32) -> Self {
        Foo {
            baz: x,
            ..Default::default()
        }
    }
}

impl With<bool> for Foo {
    fn with(x: bool) -> Self {
        Foo {
            quux: x,
            ..Default::default()
        }
    }
}

And this has roughly the same effect. Alternatively, you could implement From<String>, From<i32>, and From<bool> for Foo

3 Likes

Here you go! This will autogenerate the Fn* impls for you.

3 Likes

Amazing! I'm going to play with these ideas the next chance I get. Thank you both!

1 Like

Don't you think of making this into a crate? Or this is too special-cased?

1 Like

Sure, will do, I'll write up a crate and try to make it as ergonomic as possible! I'll get back to you in a few hours or so...

Sorry for the delay! I've been trying to figure out how to get generics to work.

2 Likes

I'm not sure if this is helpful for anyone's use case, but there's at least one way to do this with just generics:

trait FlimFlam<T> {
    fn frob(t: T);
}

struct Foo();

impl FlimFlam<String> for Foo {
    fn frob(t: String) {
        println!("{}", t);
    }
}

impl FlimFlam<(String, i32)> for Foo {
    fn frob((t, u): (String, i32)) {
        println!("{} {}", t, u);
    }
}

It's interesting that println! itself is variadic.

You could also use an HList as defined in GitHub - lloydmeta/frunk: Funktional generic type-level programming in Rust: HList, Coproduct, Generic, LabelledGeneric, Validated, Monoid and friends., but this is a pretty complex crate to depend on.

This thread clearly illustrates why we need overload for functions

1 Like

That's because it's both a macro, and it's generated by the compiler. Macros can be variadic through repetitions ($($x:tt)*).

2 Likes

Voilà! The overlodable crate. I'll make a post announcing it now.

1 Like

Fantastic!
One question, are the nightly features you're using expected to make it to stable at some point?

Perhaps at some point, although there seems to have been some progress in the respective GitHub issues recently. I'd think that this might be ready in a few months, individual features in Rust move slowly, especially if there isn't a huge push for them.

In any case, it feels nice to be riding the nightly stream once again! :surfing_man:

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.