Linear
Linear

Reputation: 22236

What are the differences between a default Trait method and a parameterized function?

In Rust, I tend to have design issues when it comes to writing modules with traits. I'm not always sure whether I want:

pub trait Foo {
    fn foo(&self) -> bool;

    fn bar(&self) {
       if self.foo() {
           // Do something!
       } else {
           // Do something else!
       }
    }
}

Or

pub trait Foo {
    fn foo(&self) -> bool;
}

pub fn bar<T>(fooer: &T) where T: Foo {
    if fooer.foo() {
        // Do something!
    } else {
        // Do something else!
    }
}

(Of course, real examples are likely to have more elaborate traits or function signatures)

While issues of design are beyond the scope of Stack Overflow, I'm not entirely sure I even understand the meaningful, objective differences between the two, and I don't feel like browsing the standard library has shed much light. It seems like Rust's standard library prefers to use variable.method() as opposed to mod::function(&variable) in most cases. However, that still doesn't really answer the question since that's just a style guide argument rather than being based on objective knowledge about the difference.

Other than the obvious syntactic difference, what are the main functional differences between a default trait method and a module-level parameterized function? One big question I have is: does the default trait method monomorphize to use static dispatch, or does if it take self as if it were a trait object?

The only difference I'm seeing off the top of my head is that an impl of the trait may elect to override the default method implementation, hopefully/presumably providing an implementation that fulfills the same contract, while I'm guaranteed that the mod::function implementation always runs the exact same code no matter what (for better or worse). Is there anything else? Does the answer change if associated types or extension traits are involved?

Upvotes: 6

Views: 1324

Answers (1)

Matthieu M.
Matthieu M.

Reputation: 300299

You actually answered the question yourself, congratulations!

Since Rust only has principled overloading/overriding via traits, the essential semantic difference is that a trait method can be overridden, and thus customized, while a free function cannot.

Technically, both Trait::func(&self) and mod::func<T: Trait>(&T) are monophormized while mod::func(&Trait) is not (and thus will incur the slight overhead of virtual calls).

Also, there is a slight memory overhead to Trait::func(&self): one more entry in the virtual table. It's probably unnoticeable.

In fine, the choice is generally a judgement call. Whether you open the door to customization or not is your choice.

Upvotes: 4

Related Questions