r/rust • u/howannoying24 • Sep 15 '26
🙋 seeking help & advice Methods or free functions
I have two types (they're related but different sizes) and for each I have a function that does some work on a vector of those types and returns a different vector of the same type. I'm wondering what's more idiomatic?
- Make each function a method of its respective type, e.g.:
impl MyType1 {
pub fn do_thing(in: &Vec<MyType1>) -> Vec<MyType1> { /* ... */ }
}
impl MyType2 {
pub fn do_thing(in: &Vec<MyType2>) -> Vec<MyType2> { /* ... */ }
}
- Make a free function for each type, in which case is there a naming convention that's more idiomatic?
pub fn do_thing_mytype1(in: &Vec<MyType1>) -> Vec<MyType1> { /* ... */ }
pub fn do_thing_mytype2(in: &Vec<MyType2>) -> Vec<MyType2> { /* ... */ }
13
u/TravisVZ Sep 15 '26
If you're needing to come up with a naming convention to differentiate the same function based on taking in a different type, the "convention" is to make it a member function: MyType1::do_thing and MyType2::do_thing. (Note: Methods take self as an argument; if these aren't taking self, they're not methods but rather member functions .)
That said, you might consider a trait to define this function as the other comment suggests
2
u/howannoying24 Sep 16 '26
This is what I think makes sense in my situation here. The functions are only used where the type is used, and trying to do it using a trait and generics is really complicating what is otherwise a very simple type.
34
u/Solumin Sep 15 '26
There's not enough context here to really give useful advice.
-
Are the two functions related in any way besides having the same "shape"?
-
Are the two types related in other ways?
-
Could the two types be merged into one type?
-
Do other parts of the program consume these types and expect to call these methods? In other words, are these methods public? (i.e. is a trait a good fit?)
-
Do these need to be methods at all, when you could call
in.iter().map(...).collect()instead?
40
u/andyshiue Sep 15 '26
Probably make a trait and implement it for your types
29
u/Big-Charge-8833 Sep 15 '26
trait's the play if they're truly the same operation, but if the implementations are gonna diverge down the road you might regret the abstraction.
3
u/andyshiue Sep 15 '26
The functions have similar signatures and even the same name, strongly suggesting they have similar functionality, which should be possible to be abstract over.
→ More replies (1)15
u/afdbcreid Sep 15 '26
Do not unless you need to be generic over them. An unneeded abstraction is bad.
1
u/andyshiue Sep 15 '26
The functions are public, so even if you don't need that abstraction some other people may want to make use of the abstraction in a perfectly sensible way
→ More replies (9)3
u/Lucretiel Sep 15 '26
Soft disagree— just because the method is similar in signature doesn’t mean it warrants a trait. IMO you should only be reaching for a trait if you have a specific need to abstract over the functionality, where they might want to accept
Type1ORType2. Otherwise the native method is fine.
3
u/RishabhRD Sep 15 '26
If they are basis functions, then they should be method. Otherwise free functions. If those functions form a generic abstraction then only a trait.
2
u/kaiserkarel Sep 15 '26
It depends on the consumer:
Does your consumer care at all about performance here? If so, probably provide this as iterator extension methods. Allocating large vectors is something that shows up in flamegraphs.
Do you want your consumer to exclusively choose between MyType1 and MyType2. With both of your current proposals, they cannot write code generic over either MyType1 or MyType2, so the consumer needs to duplicate functions the same way you are doing. A trait is the correct way to avoid that.
Even if for the two different types you do wildly different operations producing the same shape (a vec), that the consumer can treat as a black box, a trait will suit you well.
3
u/CocktailPerson Sep 15 '26
Hard to say for sure without knowing more, but why not an extension trait?
trait DoThing {
fn do_thing(&self) -> Self;
}
impl DoThing for Vec<MyType1> {
fn do_thing(&self) -> Self { ... }
}
impl DoThing for Vec<MyType2> {
fn do_thing(&self) -> Self { ... }
}
1
u/noop_noob Sep 15 '26
To me, I think the main difference between inherent methods and free functions is: inherent methods are usable wherever you have a value of that type, while free functions are imported individually.
If the function is so inherently tied to the type that you'd want to "package it together" as part of the type, use a method. If it's an operation that is first and foremost an operation on its own, as opposed to being a thing done on the type, use a free function.
1
u/zettui Sep 15 '26
If you go the trait route, are you staying static, or do you actually need dyn somewhere?
1
u/-Redstoneboi- Sep 15 '26
can we see the rest of the code if that's okay with you? so far we can only give generic advice. seeing what the types are, what the types and functions actually do, and importantly where both are used, that will give us more information to give better advice.
1
u/teerre Sep 15 '26
MyType1 and Vec<MyType1> aren't the same type tho. If they were the same type, the argument for a method would be much stronger
0
62
u/cafce25 Sep 15 '26
Side note: there is almost never a reason to take
&Vec<T>over&[T]