Struggle with Rust compiler— 5 Link to heading

Function overloading Link to heading

In C++, one can write multiple functions of the same name but with different parameters. For example, in the following example, the function list_files() is overloaded with two different argument type: list_files(Linux) and list_files(Windows).

#include <vector>
#include <iostream>

struct Linux {
  std::vector<std::string> ls() const {
    return {"/home", "/usr", "/bin"};
  }
};

struct Windows {
  std::vector<std::string> dir() const {
    return {"c:\\Windows", "c:\\Program Files"};
  }
};

std::vector<std::string> list_files(const Linux& linux) {
    return linux.ls();
}

std::vector<std::string> list_files(const Windows& windows) {
    return windows.dir();
}

int main() {
    Linux linux;
    Windows windows;
    std::cout << "Linux:";
    for (const auto& path : list_files(linux)) std::cout << " " << path;
    std::cout << "\n";

    std::cout << "Windows:";
    for (const auto& path : list_files(windows)) std::cout << " " << path;
    std::cout << "\n";
}

So far so good. Let’s try to translate it into Rust directly:

struct Linux;

impl Linux {
    fn ls(&self) -> Vec<String> {
        ["/home", "/usr", "/bin"].iter().map(|s| s.to_string()).collect()
    }
}

struct Windows;

impl Windows {
    fn dir(&self) -> Vec<String> {
        ["c:\\Windows", "c:\\Program Files,"].iter().map(|s| s.to_string()).collect()
    }
}

fn list_files(linux: &Linux) -> Vec<String> {
  linux.ls()
}

fn list_files(windows: &Windows) -> Vec<String> {
  windows.dir()
}

fn main() {
    let linux = Linux {};
    let windows = Windows {};

    println!("Linux: {}", list_files(&linux).join(" "));
    println!("Windows: {}", list_files(&windows).join(" "));
}

Unfortunately, the compiler complains because Rust does not support function overloading.

error[E0428]: the name `list_files` is defined multiple times
  --> src/main.rs:21:1
   |
17 | fn list_files(linux: &Linux) -> Vec<String> {
   | ------------------------------------------- previous definition of the value `list_files` here
...
21 | fn list_files(windows: &Windows) -> Vec<String> {
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `list_files` redefined here
   |
   = note: `list_files` must be defined only once in the value namespace of this module

OK then, how do I go about this? I certainly don’t want to have two different functions linux_list_files() and windows_list_files(). That would be a nightmare.

Well, Rust is missing function overloading but supports traits! You can define a trait, say OperatingSystem which has a method called ls().

--- before
+++ after
@@ -14,12 +14,24 @@
     }
 }
 
-fn list_files(linux: &Linux) -> Vec<String> {
-  linux.ls()
+trait OperatingSystem {
+    fn ls(&self) -> Vec<String>;
 }
 
-fn list_files(windows: &Windows) -> Vec<String> {
-  windows.dir()
+impl OperatingSystem for Linux {
+    fn ls(&self) -> Vec<String> {
+        self.ls()
+    }
+}
+
+impl OperatingSystem for Windows {
+    fn ls(&self) -> Vec<String> {
+        self.dir()
+    }
+}
+
+fn list_files<T: OperatingSystem>(os: &T) -> Vec<String> {
+    os.ls()
 }
 
 fn main() {

A trait is like a purely virtual function in C++ or an interface in Java. A trait provides an interface, which will be implemented by different types. We define OperatingSystem trait with the method ls(). We then implement the method for both Linux and Windows. We then create a single function list_files() which takes in any type that implements OperatingSystem.

I have to admit that Rust’s implementation is a bit lengthy compared to C++’s function overloading. However, Rust’s trait is a unique feature not found in C++ and is extremely powerful. I will cover how Rust’s generic with trait replaces C++ templates in a separate story, so stay tuned!