Trait method or free function?


#1

Just as a practice to learn Rust, I wrote a patch to change std::num::pow(a, b) into a trait method of a. (Usage: use std::num::Pow; a.pow(b))

What do you think are the pros and cons of this approach, or is it just a matter of taste?

What I understand about UFCS is that we will be able to change a.b() to foo::bar::b(a) at any time, but not vice versa. (It’s interesting to see that it’s an opposite of what Dlang folks call UFCS.)


#2

I assume this was meant to be a.pow(b)?

Yeah core::num has a few weird dangling functions. pow and the power_of_2 family are the weird “I have no trait version” functions, if I recall correctly. I don’t believe there’s any clear reason why this is the case. It’s just something that happened, I reckon. Personally, I think pow should be a method on the value, but @aturon is working on refactoring the whole module (basically scrapping the whole “numeric hierarchy” and just making extension traits for being generic over different sizes of ints, uints, and floats), so what’s gonna happen is up in the air.


#3

std::ptr is another module that has a lot of free functions that could easily be methods on the RawPtr traits. std is just a completely random mishmash of designs right now.


#4

So the question has been danced around, what do we think the pattern should be in general? Are free functions / traits synonymous in rust? Seems like having two ways of solving the problem is … Maybe not ideal?


#5

@Gankro thanks! I fixed it.

When we start to use an editor/IDE with semantic code completion, a.b() will have a great advantage over use foo::bar::b; b(a). IIRC .NET languages were designed with this in mind.


#6

Most free functions in std today are just historical baggage. During API stabilization, we’ve generally been deprecating these and, when appropriate, moving them to methods.


#7

Be careful in adding methods to traits. Any type that wants to implement that trait must also implement all of its methods. The Haskell community has long had problems with its numeric hierarchy not being split up enough.

My general rule of thumb is make traits small… only put the essential methods in them. Everything else should be a free function.

But that advice mostly comes from my years with Haskell. In Rust, traits are also used like classes. In that case maybe it is OK to add all the related function to the trait. I guess if you are going to use a trait like an “ability” (e.g. Iterator) then I would keep it small and lean. If you are using it like a class, design it like a class and include everything and the “kitchen sink” too:)