Demonstration of flexible function calls in Rust with function overloading and optional arguments

Overview

Table of Contents

flexible-fn-rs

Demonstration of flexible function calls in Rust with function overloading and optional arguments

What is this trying to demo?

This repo is trying to demo that Rust can provide all the flexbilities when doing function calls like any other languages. The flexibilities are demonstrated in 3 things:

  • Name and unnamed arguments
  • Optional arguments
  • Function overloading
    • Parameter overloading
    • Return type overloading

How is the code structured?

There are 3 demo, focusing on 3 kinds of functions:

  • Independent functions, calling without attaching to any struct
  • Struct functions, defined in struct impl blocks
  • Trait functions, defined in impl Trait for Struct blocks

Named/Unnamed and Optional arguments

Mechanism

We use structs as arguments to provide these functionalities. Combined with the derive_builder trait, we can have all the flexibilities when instantiating the struct:

  • only provide values to the fields we need and leave others as default
  • named arguments with named struct fields
  • unnamed arguments with tuple struct

Compared to C++

Named arguments

Like Rust, C++ does not support named arguments and also recommend using objects as arguments to achieve this

Optional arguments

Unlike Rust, C++ does support optional arguments. However, any omitted arguments must be the last argument in the argument list. Therefore, it is not really flexible and cannot provide complex optional argments list. Generally, it is also recommended to use objects as arguments to provide API with complex optional argument list in C++.

Compared to Python

Python has the most modern and flexible function call mechanism. Optional and named arguments are basic functionalities in the language. These features are used everywhere, they can be found in almost any Python API. By using structs with builder pattern as arguments, we can make Rust as close as possible to the flexbility of Python function argument lists.

Overloading

Mechanism

The only way we can have different functions with similar names in Rust is by calling them from different objects. Overloading is about calling functions with the same name with different arguments. Therefore, we can consider the different arguments as objects and implement functions with similar names on them.

Independent function

We start with a generic trait signature of the function:

pub trait F<R> { // R generic parameter for return type
    fn f(&self) -> R;
}

We can then implement this for different arguments:

// arg: () - empty
// return Result<i32>
impl F<Result<i32>> for () {
    fn f(&self) -> Result<i32> {
        Ok(1)
    }
}

// arg: (&str, i32)
// return Result<HashMap<i32, String>>
impl F<Result<HashMap<i32, String>>> for (&str, i32) {
    fn f(&self) -> Result<HashMap<i32, String>> {
        Ok(HashMap::from([(self.1, String::from(self.0))]))
    }
}

// arg: Info struct
// return Result<Vec<String>>
impl F<Result<Vec<String>>> for &arg::Info<'_> {
    fn f(&self) -> Result<Vec<String>> {
        Ok(vec![String::from("trait_fn"), format!("{:#?}", self)])
    }
}

We can then call the functions on the argument types:

let a: Result<i32> = ().f()
let b: Result<HashMap<i32, String>> = ("abc", 1).f()
let c: Result<Vec<String>> = (&arg::Info { ... }).f()

However, those function calls are ugly and unintuitive. Therefore, we should write a wrapper to make them look better:

pub fn f<P: F<R>, R>(p: P) -> R {
    p.f()
}

Now, we can call f() in the regular way:

let a: Result<i32> = f(())
let b: Result<HashMap<i32, String>> = f(("abc", 1))
let c: Result<Vec<String>> = f(&arg::Info { ... })

Struct function

We can modify this a bit to make this works as methods of another struct. The signature should now contain the struct type. The method also expose a borrowed reference to the object in other to use other fields/methods of the struct.

pub trait F<O: ?Sized, R> {
    fn f(&self, o: &O) -> R;
}

Now here is the wrapper:

impl O {
    pub fn f<P: F<Self, R>, R>(&self, p: P) -> R {
        p.f(self)
    }
}

The type of O is now Self in the wrapper. Self is unsized but generic type parameter are implicitly bounded by Sized. As a result, we remove the Sized bound for O in the signature with O: ?Sized. I am not sure if removing the Sized bound has any implication and whether there is any better way of doing this.

Now we can call the method on struct O:

let o = O {};
let a: Result<i32> = o.f(())
let b: Result<HashMap<i32, String>> = o.f(("abc", 1))
let c: Result<Vec<String>> = o.f(&arg::Info { ... })

Trait function

This is mostly similar to struct functions. However, the wrapper should not be in the impl block but should be in the trait block as the default impl

pub trait T {
    fn f<P: F<Self, R>, R>(&self, p: P) -> R {
        p.f(self)
    }
}

By having the wrapper as the default impl of trait, every struct that impl the Trait will not have to implement this wrapper again. Notice that if that is the only method of the trait, we must still provide an empty impl block.

pub struct I;

impl T for I {}

Now we can call the method of the trait on the object:

let i = I {};
let a: Result<i32> = i.f(())
let b: Result<HashMap<i32, String>> = i.f(("abc", 1))
let c: Result<Vec<String>> = i.f(&arg::Info { ... })

Compared to C++/Java

Because of this overloading mechanism (each overloaded function is a trait impl for an argument type), we will not be able to have similar function name and arguments types but different return type. This is the same as in C++/Java, we also cannot have overloaded functions with just a different return type.

Known issues

  • Error is not straight forward: when we use an overloaded method on an argument without implementing the signature for that argument, instead of method not found in ... we will receive the trait ...::F<>... is not implemented for Arg
  • Less IDE suggestions and completions
  • A bit tedious to implement compared to the traditional way of just choosing to a different function name.

Alternatives

As of now, there are 2 crates provides overloaded functions:

However, both of them seems to be outdated and unmaintained as no commit has been made for years.

You might also like...
Asynchronous runtime abstractions for implicit function decoloring.
Asynchronous runtime abstractions for implicit function decoloring.

decolor Asynchronous runtime abstractions for implicit function decoloring. Decolor is in beta Install | User Docs | Crate Docs | Reference | Contribu

Fusion is a cross-platform App Dev ToolKit build on Rust . Fusion lets you create Beautiful and Fast apps for mobile and desktop platform.
Fusion is a cross-platform App Dev ToolKit build on Rust . Fusion lets you create Beautiful and Fast apps for mobile and desktop platform.

Fusion is a cross-platform App Dev ToolKit build on Rust . Fusion lets you create Beautiful and Fast apps for mobile and desktop platform.

A Diablo II library for core and simple client functionality, written in Rust for performance, safety and re-usability

A Diablo II library for core and simple client functionality, written in Rust for performance, safety and re-usability

Code examples, data structures, and links from my book, Rust Atomics and Locks.

This repository contains the code examples, data structures, and links from Rust Atomics and Locks. The examples from chapters 1, 2, 3, and 8 can be f

List of Persian Colors and hex colors for CSS, SCSS, PHP, JS, Python, and Ruby.

Persian Colors (Iranian colors) List of Persian Colors and hex colors for CSS, SCSS, PHP, C++, QML, JS, Python, Ruby and CSharp. Persian colors Name H

 Northstar is a horizontally scalable and multi-tenant Kubernetes cluster provisioner and orchestrator
Northstar is a horizontally scalable and multi-tenant Kubernetes cluster provisioner and orchestrator

Northstar Northstar is a horizontally scalable and multi-tenant Kubernetes cluster provisioner and orchestrator. Explore the docs » View Demo · Report

Time related types (and conversions) for scientific and astronomical usage.

astrotime Time related types (and conversions) for scientific and astronomical usage. This library is lightweight and high performance. Features The f

UnTeX is both a library and an executable that allows you to manipulate and understand TeX files.

UnTeX UnTeX is both a library and an executable that allows you to manipulate and understand TeX files. Usage Executable If you wish to use the execut

A convenient tracing config and init lib, with symlinking and local timezone.
A convenient tracing config and init lib, with symlinking and local timezone.

clia-tracing-config A convenient tracing config and init lib, with symlinking and local timezone. Use these formats default, and can be configured: pr

Owner
Tien Duc (TiDu) Nguyen
Tien Duc (TiDu) Nguyen
The system for remote workers to prevent their family members from interrupting conference calls

onair The system for remote workers to prevent their family members from interrupting conference calls. The system is designed to automatically detect

Yushi OMOTE 6 Sep 21, 2022
Minimal, flexible & user-friendly X and Wayland tiling window manager with rust

SSWM Minimal, flexible & user-friendly X and Wayland tiling window manager but with rust. Feel free to open issues and make pull requests. [Overview]

Linus Walker 19 Aug 28, 2023
Flexible snowflake generator, reference snoyflake and leaf.

Flexible snowflake generator, reference snoyflake and leaf.

Egccri 2 May 6, 2022
Minimal, flexible framework for implementing solutions to Advent of Code in Rust

This is advent_of_code_traits, a minimal, flexible framework for implementing solutions to Advent of Code in Rust.

David 8 Apr 17, 2022
A flexible, simple to use, immutable, clone-efficient String replacement for Rust

A flexible, simple to use, immutable, clone-efficient String replacement for Rust. It unifies literals, inlined, and heap allocated strings into a single type.

Scott Meeuwsen 119 Dec 12, 2022
Concatenate Amazon S3 files remotely using flexible patterns

S3 Concat This tool has been migrated into s3-utils, please use that crate for future updates. A small utility to concatenate files in AWS S3. Designe

Isaac Whitfield 33 Dec 15, 2022
Rust macro to make recursive function run on the heap (i.e. no stack overflow).

Decurse Example #[decurse::decurse] // ?? Slap this on your recursive function and stop worrying about stack overflow! fn factorial(x: u32) -> u32 {

Wisha W. 18 Dec 28, 2022
Async variant of Tonic's `interceptor` function

Tonic Async Interceptor This crate contains AsyncInterceptor, an async variant of Tonic's Interceptor. Other than accepting an async interceptor funct

Arcanyx Technical Wizardry LLC 4 Dec 20, 2022
Select any exported function in a dll as the new dll's entry point.

Description This tool will patch the entry point of the input dll and replace it with the RVA of another exported function in that same dll. This allo

Kurosh Dabbagh Escalante 43 Jun 7, 2023
The lambda-chaos-extension allows you to inject faults into Lambda functions without modifying the function code.

Chaos Extension - Seamless, Universal & Lightning-Fast The lambda-chaos-extension allows you to inject faults into Lambda functions without modifying

AWS CLI Tools 5 Aug 2, 2023