Rust is Not a Functional Language

Rust is not a functional language

There seems to be some confusion about whether Rust can also be classified as a functional language. This article aims to clarify this issue. To give away the conclusion in advance: Rust is not a functional language.

This does not imply that Rust has any shortcomings: it excels in its design goals. Nevertheless, while Rust borrows some concepts from functional programming paradigms, its core does not adhere to this paradigm. There is nothing wrong with this; not every language needs to follow a single programming paradigm. However, calling Rust a functional language underestimates both Rust itself and the value of functional programming (FP).

So, what exactly is a functional language?

To determine whether Rust can be considered a functional language, we first need to understand what is meant by the term “functional language.” Unfortunately, there is no simple and clear definition. There are several possible interpretations, ranging from overly strict to relatively loose. Let’s take a look at a few of them.

Theoretical Foundation

The strictest definition may be based on the theoretical foundation of the language. In short, if a language uses λ-calculus as its theoretical framework, it can be considered a functional language. However, this definition ultimately proves to be too stringent.

On one hand, Lisp is widely regarded as a functional language, but strictly speaking, it is not based on λ-calculus. Therefore, according to this definition, Lisp cannot be classified as a functional language.

On the other hand, not all programming languages are built on a strict theoretical framework, which means that for those languages, the above definition does not apply at all. This includes many popular languages, such as JavaScript, C, C++, and of course, Rust.

Even so, there are some languages that fit this definition: such as Haskell, various ML variants, Agda, Idris, Coq, etc.

First-Class FunctionsA commonly mentioned definition of a functional language is “a language with first-class functions.” This means that functions can be passed around like ordinary values without any restrictions. This definition does capture one of the cores of functional programming. However, in practice, it is somewhat broad and not sufficiently precise.

First, this definition is not entirely accurate: it does not specify what we mean by “function.” If we are talking about “something that can take parameters and produce results,” then function pointers also fit the criteria, which would mean that C, due to having first-class function pointers, would be a functional language. This is clearly too broad.

If we specify “function” as “closure,” meaning a function that can capture its environment (i.e., the variables within the scope at the time of definition), we can narrow the scope. In practice, most functional languages do not strictly differentiate between the two. This statement is more practical, but it may still be surprising: JavaScript supports first-class closures, so according to this definition, it is also a functional language.

About JS and Functional StyleHere we slightly deviate from the topic… It is useful to distinguish between the functional programming paradigm and the functional programming style. The former refers to the conceptualization and thinking approach to programming, while the latter refers to a specific style of writing code. Naturally, there is some overlap between the two, but there is an important distinction in terms of languages. Languages that support or are based on the functional paradigm typically encourage (or even require) thinking about programs in a certain way, which naturally leads to writing code in a functional style. On the other hand, languages that only support that style require a great deal of discipline to adhere strictly to it—because these languages not only do not encourage this way, but sometimes seem to actively resist it.

For those who wish to further refine the definition of “first-class closures,” additional language features can be added as prerequisites. This approach is quite popular and descriptive. Below are some more commonly referenced language features.

ImmutabilityThis is a frequently mentioned feature. Broadly speaking, immutability mainly concerns data structures and variables. By immutable data structures, we mean that if we need to modify an item in a list, we create a new—typically shallow-copied—list that contains the changed elements. As for immutable variables, it is technically a misnomer; we refer to the absence of so-called “variables”—once assigned, they cannot change. The formal term should be “binding,” which means binding a computation (or its result) to a name—then it can be reused wherever that computation is represented.

However, this is somewhat tongue-in-cheek: while immutability promotes good functional style, it is neither strictly necessary nor required by practical languages. For example, in Haskell, values are considered immutable, but if mutability is needed, it can be obtained through STRef, IORef, MVar, etc.—though they are only valid in specific contexts (specifically, within certain monads).

The key point is that if something is actually mutable, its effects should not be visible externally beyond the final result of the computation, to avoid “infecting” the rest of the code. This naturally leads to…

Function PurityFunction purity refers to the property of functions; to be a pure function, it must have no side effects. In other words, functions, in the strict mathematical sense, are functions: they map inputs to outputs and do nothing else—they do not modify memory or perform I/O operations.

This also implies a certain degree of immutability, as externally visible changes are indeed side effects. However, complete strict purity is entirely impractical. After all, I/O is a side effect, so a programming language with absolutely no side effects cannot do anything interesting—like printing results to the screen.

Referential TransparencyReferential transparency is more a result of purity and immutability. In short, this means that any name can be replaced with its definition or the result of its final evaluation without changing the observable behavior of the program (in practice, performance is usually not considered). This greatly enhances code readability: one can understand what a given name represents without needing to know all code paths. However, one point to note is that name shadowing (i.e., defining a name that shadows another name within the same scope) can complicate matters: if name shadowing is misused, readability can suffer significantly.

This is not a “language feature,” but a direct consequence of using a functional style. Nevertheless, languages that promote referential transparency are closer to the ideal functional language than those that do not.

Higher-Order FunctionsHigher-order functions are functions that take other functions as parameters, such as map, fold, etc.

This is a natural result of first-class functions. However, it usually implies that the language must at least support rank-1 parametric polymorphism (or dynamic typing/untyped), and using HOFs should be ergonomically reasonable without sacrificing type safety. This leads to…

Parametric PolymorphismParametric polymorphism is difficult to explain in a single sentence. In short, it allows us to use so-called type variables in type signatures. At the point of use, the type variable is instantiated to a specific type. The implementation must be independent of the specific instance. This part distinguishes it from ad-hoc polymorphism, which is function overloading, where each specific instance has a unique implementation, thus not being generic, meaning that overloaded functions can only be called with types that have overloads.

In many programming languages, parametric polymorphism is more commonly known as generics, while in C++, it is templates. There are some subtle differences, but both can encode at least rank-1 parametric polymorphism.

Parametric polymorphism is a feature of the type system, so it only applies to strictly statically typed languages. If a language is strictly statically typed, then for HOFs to be useful, parametric polymorphism is absolutely necessary, so this is a hard prerequisite.

Click 👇 to follow

Like + Share + View to quickly improve programming skills👇

Reference link: https://serokell.io/blog/rust-is-not-a-functional-language

Leave a Comment