Overloading Functions

Rust does not have traditional function overloading.

In some other langauges, the argument types and the number of arguments can lead to unique function signatures and effectively allow the same name to be used:

class User {
  void doSomething(int id) {
  }

  void doSomething(String id) {
  }

  void doSomething(int id, String name) {
  }
}

While all 3 methods share the same doSomething name, they can each do something unique and the only thing in common could be the method name.

Rust does not allow multiple function definitions with the same name. So Rust does not allow multiple functions with the same name to vary with the number of arguments or the types of parameters.

Instead, Rust functions can use generics to overload a function.

Effectively Overloading via Generics

#![allow(unused)]
fn main() {
fn doSomething<T: AsRef<str>>(s: T) {
  print!("{}", s.as_ref());
}

doSomething(String::from("Hello"));

doSomething(&" world");

let s = "!".to_string();
let s = &s;
doSomething(s);

struct MyStruct {
  inner: String,
}

impl AsRef<str> for MyStruct {
  fn as_ref(&self) -> &str {
    &self.inner
  }
}

let s = MyStruct { inner: "Hello world!".to_string() };
doSomething(&s);
}

The doSomething function uses generics to accept many different types. There are many standard traits such as Into, TryInto, AsRef, and AsMut which are useful as generic type parameter constraints. (Note: A type should implement From instead of Into.)

Different function names

An alternative of course is to have different names for the functions. Instead of doSomething, have doSomethingWithI64 and doSomethingWithStr.

Of course, the functions could actually do something completely different. A nice property of having only one function with generic type parameters is the function definition is the same regardless of the concrete types used (hence a generic function).

If differently named functions only do type conversions, then it is often better to use a generic function.

#![allow(unused)]
fn main() {
fn calculate_with_string(value: String) {
  calculate_with_str(&value)
}

fn calculate_with_str(value: &str) {
  println!("{}", value);
}
}

The above functions are a code smell when a single generic function is possible.

#![allow(unused)]
fn main() {
fn calculate<T: AsRef<str>>(value: T) {
  println!("{}", value.as_ref());
}
}

On the other hand, there are times when function implementations are not generic. Constructors are functions which can often have different implementations.

#![allow(unused)]
fn main() {
struct MyType {
  value: i64
}

impl MyType {
  fn with_str<T: AsRef<str>>(value: T) -> Result<Self, std::num::ParseIntError> {
    Ok(MyType { value: value.as_ref().parse()? })
  }

  fn with_i64(value: i64) -> Self {
    MyType { value }
  }
}
}

The with_str function is failable and requires additional processing while the with_i64 is simple and merely wraps the value. While overly simplistic, there are many constructors which operate differently enough that it is easily justifiable to have differently named functions.