sunnyone
sunnyone

Reputation: 1530

Is it recommended to use traits to implement utility functions for structs in external crate?

I want to implement a simple utility/helper function in Rust. The function just concatenates the path in a struct (from an external crate) and the argument passed. Is it more idiomatic to implement the helper-function as a normal function or as function of a custom trait?

The implementation of the trait-based approach:

use std::path::{Path, PathBuf};

pub trait RepositoryExt {
    fn get_full_path(&self, path_in_repository: &Path) -> PathBuf;
}

impl RepositoryExt for othercrate::Repository {
    // othercrate::Repository's workdir() returns its path
    fn get_full_path(&self, path_in_repository: &Path) -> PathBuf {
        self.workdir().join(path_in_repository)
    }
}

With just a function:

pub fn get_repository_full_path(repo: othercrate::Repository,
                                path_in_repository: &Path) -> PathBuf {
    repo.workdir().join(path_in_repository)
}

The trait-based approach shortens the code when using the helper-function, but I'm worried that it may introduce difficulty to understand where it's defined.

Though both implementations should work, I want to know which is the recommended way in Rust.

Upvotes: 3

Views: 2047

Answers (1)

Lukas Kalbertodt
Lukas Kalbertodt

Reputation: 88656

(Disclaimer: I am not entirely sure about this. If this answer receives enough™ upvotes, I will delete this disclaimer)


Good question! I have already seen both solution in the wild and would say that both are valid to use. Or in other words: neither of the two solutions are considered bad.

However, I'd say that using the Ext-trait approach is often a slightly better choice due to these advantages:

  • Many operations feel way more natural to call "on an object" (with dot-notation) than to call a function with both objects.
  • Chaining multiple calls looks nice in code because it fits our left-to-right way of reading, whereas with the function-approach the code is harder to read: f(f(a, f(d, e)), c).
  • If the user prefers the plain-function style, he can also use it that way with Trait::func(self_object, arg).

But of course there are some disadvantages (you already mentioned one):

  • It's harder for the user to understand where the helper-function is defined.
  • The user needs to have the trait in scope (read: use the trait).

Upvotes: 4

Related Questions