Idiomatic Rust favors Functional or Imperative Style?

In general, which style Rust favors by default - the imperative style as in for loop and let mut
OR
functional style as in map and filter and reduce?

How do you choose otherwise which style to use?
Are there any significant performance differences?

I would prefer the functional style but wanted to know generally speaking when writing rust code experienced rust developers prefer which style?

Thanks.

3 Likes

Just as Bruce Lee practiced Jeet Kune Do, the style of all styles, Rust is not bound to any one paradigm.

Instead of trying to put it into an existing box, it's best to just feel it out. Rust isn't Haskell and it's not C. It has aspects in common with each and it has traits unique to itself.

One particularly-interesting facet of Rust is that you can program imperatively with some of the same benefits as functional programming, because of immutability by default, and because of mutability aliasing checking. You can have efficient, imperative data structures which are as safe to use as immutable structures. In some ways this is more work than the equivalent functional code, and safer than the equivalent imperative code. Whether it's worth it depends.

15 Likes

So basically you are saying go with any style :sunglasses: and it will still be great since immutability is enforced by default and when using mutability it will also be safe with the ownership rules.

I guess I agree with you.
The key point is as in FP style languages like clojure there is immutability by default.
However, in rust, the immutability is NOT dependent on using functional style.
As a best practice, one should limit mutability and rust conveys that by its ownership rules.
The difference with languages like ruby is that they also allow both styles but then they don't keep mutability safe like rust does.

Sometimes giving options is more of a burden to decide but in this case if we are getting similar safety and performance results, then answer is :

do what you prefer and if you are in a team try to make everyone consistent with a given style.

In my case, I would use mutability only when needed. Use FP style in general.

Thanks for your inputs.I think I got my answer :man_dancing:

4 Likes

In my experience, most seasoned Rustaceans will do the same. The benefit of FP style is that in many cases it is easier to understand the high-level goal of the piece of code when you read it, and it limits the amount of mutable variables you need to explicitly worry about. The iterator adapters handle that for you in the background.

But... sometimes the imperative code will be faster. If you realize your code is too slow, you can bench it with cargo's built-in tool or an external crate to see where the slow parts are. You can then also compare the imperative vs functional code directly. For the most part, however, you shouldn't need to worry about speed, but just write what is easiest to understand and hardest to screw up. (And to make things more complicated, "what is easiest to understand and hardest to screw up" will depend on your experience. But that's a whole other debate :wink: )

10 Likes

Well said @L0uisc :smiley:

1 Like

I think it promotes the functional style flavor. But, considering that it's a multiparadigm language, the final decision depends on the programmer.

2 Likes

Functional style, such as iterators, often provide more bounds and other constraint information to rustc than imperative style, leading to improved optimization (in release mode) over imperative style.

9 Likes

Iterators are also composable and can be returned from functions, which is useful for abstraction.

2 Likes

I think the answer is both, really. One thing I see decently often is mutability inside a function but functional across functions, since mutability is easier to follow in-the-small, especially with ownership guards, and can be more efficient.

3 Likes

That is what I meant when I mentioned - "In my case, I would use mutability only when needed. Use FP style in general."

For example, Inside function locally if it makes sense, would prefer FP otherwise.

1 Like

Please see Persistent Data Structure Support question that is a continuation from this question.

I’m going to throw my 2¢ in here. Prefer a functional style unless the equivalent imperative style is simpler. For an example of when that might be the case, see this Stack Code Review question.

6 Likes

rubberduck203,

Yes please.

This craze for "functional" style makes me crazy. That stackexchange link demonstrates it nicely. The op there has a perfectly fine, simple loop with the expected divides and modulos in it. Clear as a bell. Then comes the "functional" version, which gets to_digit() to do all the work and then wraps it all up in a mess of .chars()....filter()...map()...collect().

Why for goodess sake?

And see what a mess this recent poster here gets into solving a simple problem: Can this code be any simpler?

2 Likes

To be clear, I do prefer a functional style. In most cases, I find a filter/map/reduce pipeline to be substantially clearer than imperative loops, but there are some cases, like unfolding or custom aggregates that end up being easier to read in an imperative style.

It’s also my understanding that iterators in Rust perform better in most scenarios.

4 Likes

What is clear to one person is not at all clear to another. For someone from, for example, a Haskell background, iterator methods are easily comprehensible - they're plain, declarative instructions of how an iterator is transformed from step to step. For someone from a C background, though, they can seem to be needless abstraction over how a loop actually does what it does. Neither of these is the wrong way of seeing it, and accordingly, Rust supports both declarative methods on immutable (for a loose definition of it) iterators and iterative loops mutating variables.

3 Likes

I guess I have a lifetime of procedural programming weighing me down.

1 Like

As said already, you can always do things the way you want to and there are cases where it’s arguably clearer to use imperative looping constructs. However there are also cases where it’s objectively more composable to use iterators.

Just do what makes sense. I say “it depends” because I think trying to nail down exactly when one case is better than another and summarize all that in a forum thread isn’t worth the effort.

6 Likes

ajnye,

"It depends...". Looks like you are right. And there seems to be a fair dollop of personal preference and familiarity one way or the other mixed in with any technical merits.

The upshot of this discussion is that I'm very happy that Rust does not dictate one way or the other and I can explore approaches I may never have considered before at my leisure.

I'm getting the feeling that Rust is defining a paradigm all of it's own. It does not quite fit in any of the procedural, imperative, functional, declarative definitions as seen on Wikipedia for example. Meanwhile all this borrow checking, anti-aliasing, lifetime business is something none of the above have (AFAICT).

7 Likes

Rust seamlessly blends imperative for loops with functional iterators. As several people have already mentioned, the resulting style can only be thought of as a blend/hybrid of the two approaches.

1 Like

I find https://boats.gitlab.io/blog/post/notes-on-a-smaller-rust/ to contain a really interesting and accurate take on the functional vs imperative discussion WRT Rust.

Rust works because it enables users to write in an imperative programming style, which is the mainstream style of programming that most users are familiar with, while avoiding to an impressive degree the kinds of bugs that imperative programming is notorious for. As I said once, pure functional programming is an ingenious trick to show you can code without mutation, but Rust is an even cleverer trick to show you can just have mutation.

In particular, Rust shows that 'single mutability' (e.g. 1 writer xor N readers) is enough to avoid most of the bugs encountered with mutability.

4 Likes