Home > Net >  Optional generic type Parameter for fn?
Optional generic type Parameter for fn?

Time:11-15

I would like a function to have an optional generic type Parameter to do something like this:

fn main() {
    bar::<()>();
}

fn bar<F: Foo>() {
    let x = some_computation();
    if F != () {
        let foo = F::new(x);
        foo.foo();
    }
}

trait Foo {
    fn new(x: u64) -> Self;
    fn foo(&self);
}

Is there a way to have an optional type parameter? And if so is there a way to check the type parameter for its presence inside the function?

I guess the answer is no, but is it possible to do that with macros then?

CodePudding user response:

In general, you can not specify a default type parameter for functions, and you can't differentiate between type parameters except through methods on the trait.

The simplest workaround for this is to just have two functions, such as bar and bar_with, where one takes no type parameters and the other takes one type parameter:

// call with no type parameters
pub fn bar() {
    let x = some_computation();
}

// call with type parameter
pub fn bar_with<F: Foo>() {
    let x = some_computation();

    let foo = F::new(x);
    foo.foo();
}

If the functions are sufficiently complex, you can have a private helper function that both the functions call:

// call with no type parameters
pub fn bar() {
    bar_inner(|_x| { /* do nothing */ })
}

// call with type parameter
pub fn bar_with<F: Foo>() {
    bar_inner(|x| {
        let foo = Foo::new(x);
        foo.foo();
    })
}

fn bar_inner<Func: FnOnce(u64)>(func: Func) {
    let x = some_computation();
    func(x);
}

Alternatively, you can solve this with an additional trait that is implemented for all Foo types, and a default type (()):

trait BarArg {
    fn bar_inner(x: u64);
}

impl<F: Foo> BarArg for F {
    fn bar_inner(x: u64) {
        let foo = foo::new(x);
        foo.foo();
    }
}

impl BarArg for () {
    fn bar_inner(_x: u64) {
        // do nothing
    }
}

fn bar<B: BarArg>() {
    let x = some_computation();
    B::bar_inner(x)
}

CodePudding user response:

You cannot match on types themselves, but from what it looks like, you want different functionality depending on the type. The best way to do that is to impl Foo for whichever types you want to be able to handle. Here is my interpretation of your code example:

fn main() {
    let a = Bar::from(()).some_completion();
    let b = Bar::from(1234).some_completion();
}

trait Foo {
    fn some_completion(&self) -> Self;
}

struct Bar {}

impl From<u64> for Bar {
    fn from(x: u64) -> Self {
        todo!();
    }
}

impl From<()> for Bar {
    fn from(x: ()) -> Self {
        todo!();
    }
}

impl Foo for Bar {
    fn some_completion(&self) -> Self {
        Self { /* something different */ }
    }
}

If you do need the generic function though, std::str::parse is a good example on how you pass a type in (a numeric type, in this case) and that changes the return type.

CodePudding user response:

I know now! :D I can just provide a noop implementation of Foo:

pub struct NoopFoo {}
impl Foo for NoopFoo {
    fn new(_: u64) -> Self { NoopFoo {} }
    fn foo(&self) {}
}

I'm still curious if this could be solved with a macro though...

  • Related