When to use a method?

Since I just got started with Rust, I would like to know when and why more experienced programmers use a method instead of defining a function taking the struct as a parameter . Because most of my experience is in functional programming languages I usually tend to favor the latter, but after checking various projects it seems the former is more commonly used.

The Rust book states:

The main benefit of using methods instead of functions, in addition to using method syntax and not having to repeat the type of self in every method’s signature, is for organization. We’ve put all the things we can do with an instance of a type in one impl block rather than making future users of our code search for capabilities of Rectangle in various places in the library we provide.

Couldn't this same main benefit be obtained by simply using the namespace provided by a module?

From my experience with other multi-paradigm languages, I would use the method mainly when:

  • I need type-based polymorphism
  • I have a stateful entity which state I would like to encapsulate and mutate only via public methods
  • There is a very strong relationship between the data and the behavior, for example when the data represents an object (let's say a car) which could perform some actions (drive, stop, etc.).

However, from what I've seen so far it seems that a function needing a struct is enough to justify implementing it as a method of such struct. This leads often to structs containing a single method, which some consider an awkward design in other languages. **Is this not the case in Rust?

A method is a function taking the struct as a parameter, the struct is just included as the first parameter and marked as a special self parameter. This enables the use of method call syntax, where the method can be inferred from the type of the first argument. This means you can write
x.foo(y) instead of Type::foo(x, y), which has the advantage that it requires less type annotation and allows for easy chaining of methods (such as x.foo(y).bar()). There is nothing to stop you using the more explicit form, but having the option of using method call syntax is usually good.

Firstly, while the fact that a method is attached to a type is helpful for organisation, but this isn't unique to methods. By adding a function in an impl block but not including a self parameter, you can create an associated function that belongs to the type but can't be called with method call syntax, so you must always call it as Type::foo(x). The main use case for associated functions is for functions that construct a type, such as Type::new().

The reason why you would do this at the level of types and not modules is that a module may contain multiple types, which may have methods of the same name. For example, in the standard library there is std::vec::Vec::as_slice() and std::vec::IntoIter::as_slice(). These methods do similar things on two very closely related but different types, so it makes sense for the types to be in the same module and have a method of the same name. Only defining the method on the type allows for them to be disambiguated easily.

Type-based polymorphism is provided by the traits in Rust, either by bounding a generic by a trait for static dispatch (fn<T : Trait> foo(x : T)), or creating a trait object for dynamic dispatch (Box<dyn Trait>). Traits have associated functions and methods attached to them because traits are attached to types, so any function that comes from implementing a trait is a method or associated function. This is separate from methods and associated functions defined directly on a type, which don't yield polymorphism because there is nothing to abstract over.

In Rust encapsulation happens at the level of modules rather than types, so this isn't necessarily a reason to define a function as a method. However, methods disambiguate where two types have the same method and one needs access to the others private fields, as with the as_slice() example above.

I think this last reason is mainly why methods are used in Rust, but I would say it is best viewed as a method doing something to a type or providing a certain view of a type, rather than the type doing something. The idea is to have methods attached to a type because they operate specifically on that type and because you don't want to have to specify the type or module when that is clear from the type of the value being passed in. I'm not sure what examples of types with a single method you have found, and whether it is good design or not would really depend on the individual case. There are plenty of examples of functions outside of types where it wouldn't make sense for them to be attached to a type, or where the name is unlikely to clash with anything else and it would be better attached to the module.

3 Likes