Rust vs. Haskell
Tags: rust, haskell
Published: 2023-02-14 20:00:34

---
title: Rust vs. Haskell
published: true
date: 2023-02-14 00:00:00 UTC
tags: rust, haskell
canonical_url: https://serokell.io/blog/rust-vs-haskell
---

Rust and Haskell don’t shy away from powerful features. As a result, both languages have steep learning curves compared with other languages. Trying to learn Rust or Haskell can be frustrating, especially in the first couple of months.

But if you already know Rust, you have a head start with Haskell; and vice versa.

In this article, we want to show how knowledge of one of these languages can help you get up to speed with another.

We won’t cover all the similarities or differences and won’t talk about language domains. Our main goal is to show the bridge between the languages; you can decide whether you want to walk it.

We won’t cover syntax as well, but get ready to switch between indentation and braces, as well as read code in opposite directions. 😉

## Basic concepts

Haskell and Rust have both been influenced by the [ML](https://en.wikipedia.org/wiki/ML_(programming_language)) programming language. ML has strong static typing with type inference, and so do Haskell and Rust.

There are other similarities:

- algebraic data types;
- pattern matching;
- parametric polymorphism;
- ad-hoc polymorphism.

We’ll cover all of these later in the article, but first, let’s talk about compilers. Both languages focus on safety – they are extremely good at compile-time checks.

### Type system

If you’re coming from one of these languages, we don’t have to convince you that types are our friends: they help us avoid silly mistakes and reduce the number of bugs.

Rust and Haskell have similar type systems. Both support conventional basic types, such as integers, floats, booleans, strings, etc. Both make it easy to create new types, use newtypes, and type aliases.

* * *

🙂 When using strings, Rust beginners puzzle over `String` vs. `&str`, and Haskell beginners puzzle over `String` vs. `Text` vs. `ByteString`.

* * *

Rust can infer types when possible.

```
let bools = vec![true, false, true];
let not_head = bools[0].not(); // false, has type bool

```

But omitting function parameter types is not allowed.

```
fn get_double_head(ints) {
// ^ error: expected one of `:`, `@`, or `|`
// note: anonymous parameters are removed in the 2018 edition (see RFC 1685)
// help: if this is a parameter name, give it a type
// help: if this is a type, explicitly ignore the parameter name
    ints[0] * 2
}

```

Omitting function return types is also not allowed.

```
fn get_double_head(ints: Vec<i32>) {
    ints[0] * 2
// ^^^^^^^^^^^ expected `()`, found `i32`
// help: try adding a return type: `-> i32`
}

```

In Rust, we always have to specify both:

```
fn get_double_head(ints: Vec<i32>) -> i32 {
    ints[0] * 2
}

```

Haskell can infer types when they’re not ambiguous.

```
let bools = [True, False, True]
let notHead = not (head bools) -- False, has type Bool

```

We don’t have to annotate function parameters and return types.

```
getDoubleHead ints =
  head ints * 2

```

But it usually results in a warning, and adding a type signature is a good practice.

```
getDoubleHead :: [Integer] -> Integer
getDoubleHead ints =
  head ints * 2

```

* * *

💡 **Note:** You can test the Haskell code snippets by pasting them in the [REPL](https://downloads.haskell.org/ghc/latest/docs/users_guide/ghci.html). Rust doesn’t have an interactive environment, so you have to reorganize the snippets and use the `main` function if you want to give them a try.

* * *

### Variables and mutability

Rust variables are, by default, immutable – when you want a mutable variable, you have to be explicit.

```
let immutable_x: i32 = 3;
    immutable_x = 1;
// ^^^^^^^^^^^^^^^ error: cannot assign twice to immutable variable


let mut mutable_x = 3; // 3
mutable_x = 1; // 1
mutable_x += 4; // 5

```

There are no mutable variables in Haskell – the syntax has no such thing as a reassignment statement. However, the value to which the variable is bound may be a mutable cell, such as [`IORef`](https://hackage.haskell.org/package/base-4.17.0.0/docs/Data-IORef.html), [`STRef`](https://hackage.haskell.org/package/base-4.17.0.0/docs/Data-STRef.html), or [`MVar`](https://hackage.haskell.org/package/base-4.17.0.0/docs/Control-Concurrent-MVar.html). The type system tracks mutability, and Haskell does not require a separate `mut` keyword.

* * *

💡 Note that Haskell allows name shadowing. But it’s discouraged and can be caught with a warning.

```
let immutable_x = 3
let immutable_x = 1
-- ^^^^^^^^^^^ warning
-- This binding for ‘immutable_x’ shadows the existing binding

```

In Rust, name shadowing is considered idiomatic. For example, the following code snippet reuses `x` and `y`:

```
// parse a string of the form "42,20"
let (x, y) = s.split_once(',').unwrap();
let x: i32 = x.parse().unwrap();
let y: i32 = y.parse().unwrap();

```

* * *

Haskell relies heavily on purely functional data structures, operations on which return new versions of data structures, while the original reference stays unmodified and valid.

For example, if we have a map and want to do some operation on a slightly modified map, we can have a value that keeps the old map but also works with the new map (without much performance cost).

```
import qualified Data.HashMap.Strict as HashMap

let old = HashMap.fromList [("Donut", 1.0), ("Cake", 1.2)]
let new = HashMap.insert "Cinnamon roll" 2.25 old

print old
-- Prints: fromList [("Cake", 1.2),("Donut", 1.0)]
print new
-- Prints: fromList [("Cake",1.2),("Cinnamon roll", 2.25),("Donut", 1.0)]

```

* * *

💡 Yes, that’s how Haskell prints a map. Yes, it’s weird.

The [`print`](https://hackage.haskell.org/package/base-4.17.0.0/docs/Prelude.html#v:print) function converts values to strings by using [`show`](https://hackage.haskell.org/package/base-4.17.0.0/docs/Prelude.html#v:show) and outputs it to the standard output. The result of [`show`](https://hackage.haskell.org/package/base-4.17.0.0/docs/Prelude.html#t:Show) is a syntactically correct Haskell expression, which can be pasted right into the code.

* * *

In Rust, standard operations mutate the collection, so there’s no way to access its state before the modification.

```
use std::collections::HashMap;

let mut prices = HashMap::from([("Cake", 1.2), ("Donut", 1.0)]);
prices.insert("Cinnamon roll", 2.25);

println!("{:?}", prices); 
// Prints: {"Cake": 1.2, "Donut": 1.0, "Cinnamon roll": 2.25}

```

* * *

**💡 What is `{:?}`?**

We can use it to debug-format any type. It relies on the [`fmt::Debug`](https://doc.rust-lang.org/std/fmt/trait.Debug.html) trait, which **should** be implemented for all public types.

* * *

If we want to emulate Haskell when working with default collections, we have to [`clone`](https://doc.rust-lang.org/std/clone/trait.Clone.html) the old one.

```
use std::collections::HashMap;

let old = HashMap::from([("Cake", 1.2), ("Donut", 1.0)]);
let mut new = HashMap::new();

new.clone_from(&old);
new.insert("Cinnamon roll", 2.25);

println!("{:?}", old); 
// Prints: {"Cake": 1.2, "Donut": 1.0}
println!("{:?}", new); 
// Prints: {"Cake": 1.2, "Donut": 1.0, "Cinnamon roll": 2.25}

```

* * *

💡 Note that persistent data structures in Haskell are usually designed for cheap cloning, so most operations have `O(log n)` complexity. In Rust, we don’t tend to clone that often, so access and mutation typically have `O(1)` and cloning – `O(n)`.

* * *

💡 Haskell has mutable constructs (such as [`STArray`](https://hackage.haskell.org/package/array-0.5.4.0/docs/Data-Array-ST.html) and [`IORef`](https://hackage.haskell.org/package/base-4.17.0.0/docs/Data-IORef.html)), which you can use when you need mutability.

* * *

## Algebraic data types (ADTs)

In simple terms, ADTs are a way to construct types. Haskell uses the single keyword `data` to declare product and sum types, while Rust uses `struct` and `enum`.

To learn more about algebraic data types, check out our [article on ADTs](https://serokell.io/blog/algebraic-data-types-in-haskell) in Haskell.

### Product types (structs)

In Rust, product types are represented by structs, which come in two forms:

- tuple structs;
- structs with named fields.

```
// A tuple struct has no names:
struct SimpleItem(String, f64);

// A struct has named fields: 
#[derive(Debug)]
struct Item {
    name: String,
    price: f64,
}

```

Which is quite similar to Haskell’s datatypes and records.

```
-- A simple datatype with no names:
data SimpleItem = SimpleItem String Double

-- A record has named fields: 
data Item = Item
  { name :: String
  , price :: Double
  }
  deriving (Show)

```

Note that `#[derive(Debug)]` corresponds to `deriving (Show)`.

In Haskell, we have to provide a type constructor (the name of our type) and a data constructor (used to construct new instances of the type), which are the same as in the previous snippet.

#### Creating instances of types

We can create instances of product types. In Rust:

```
// Creating a tuple struct:
let simple_donut = SimpleItem("Donut".to_string(), 1.0);

// Creating an ordinary struct:
let cake = Item {
    name: "Cake".to_string(),
    price: 1.2,
};

```

In Haskell:

```
-- Creating an ordinary datatype:
let simpleDonut = SimpleItem "Donut" 1.0

-- Creating a record:
let donut = Item "Donut" 1.0
let cake = Item{name = "Cake", price = 1.2}

```

We can use either the ordinary syntax or the record syntax to create records in Haskell.

#### Getting field values

In Rust, we can get the value of a field by using dot notation.

```
let cake_price = cake.price;
println!("It costs {}", cake_price); 
// Prints: "It costs 1.2"

```

In Haskell, we have been using field accessors (basically, getters), such as `price`:

```
let cakePrice = price cake
print $ "It costs " ++ show cakePrice 
-- Prints: "It costs 1.2"

```

But since GHC 9.2, we can use dot notation as well.

* * *

💡GHC is the Glasgow Haskell Compiler, the most commonly used Haskell compiler.

* * *

```
let cakePrice = cake.price
print $ "It costs " ++ show cakePrice 
-- Prints: "It costs 1.2"

```

* * *

**🤑 What is `$`?**

We use the dollar sign (`$`) to avoid parentheses.

Function application has higher precedence than most binary operators. The following usage results in a compilation error:

```
-- These are the same:
print "It costs " ++ show cakePrice

(print "It costs ") ++ (show cakePrice)

```

`$` is also a function application operator (`f $ x` is the same as `f x`). But it has very low precedence. The following usage works:

```
-- These are the same:
print $ "It costs " ++ show cakePrice

print ("It costs " ++ show cakePrice)

```

* * *

#### Updating field values

In Rust, if the struct variable is mutable, we can change its values.

```
let mut cake = Item {
    name: "Cake".to_string(),
    price: 1.2,
};

cake.price = 1.4;

println!("{:?}", cake); 
// Prints: Item { name: "Cake", price: 1.4 }    

```

In Haskell, a record update returns another record, and the original one stays unchanged (as we’ve covered in the mutability section).

```
let cake = Item{name = "Cake", price = 1.2}

let updatedCake = cake{price = 1.4}

print cake
-- Prints: Item {name = "Cake", price = 1.2}
print updatedCake
-- Prints: Item {name = "Cake", price = 1.4}

```

If we want to do something similar in Rust, we can use struct update syntax to copy and modify a struct.

```
let pricy_cake = Item { price: 1.6, ..cake };

println!("{:?}", pricy_cake);
// Prints: Item { name: "Cake", price: 1.6 }

```

Both languages have a simplified field initialization syntax if there are matching names in scope:

```
let name = "Cinnamon roll".to_string();
let price = 2.25;

let cinnamon_roll = Item { name, price }; // instead of “name: name”

```

To use it in Haskell, you have to enable [`GHC2021`](https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/control.html#extension-GHC2021) or the [`NamedFieldPuns`](https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/record_puns.html) extension.

```
let name = "Cinnamon roll"
let price = 2.25

let cinnamonRoll = Item{name, price} -- instead of “name = name”

```

### Sum types (enums)

Here is an example of a simple sum type:

```
#[derive(Debug)]
enum DonutType {
    Regular,
    Twist,
    ButtermilkBar,
}

let twist = DonutType::Twist;


data DonutType = Regular | Twist | ButtermilkBar
  deriving (Show)

let twist = Twist

```

The variants don’t have to be boring and can contain fields (which can be unnamed or named).

For example, we can have a `Donut` with `DonutType`:

```
#[derive(Debug)]
enum Pastry {
    Donut(DonutType),
    Croissant,
    CinnamonRoll,
}

let twist_donut = Pastry::Donut(DonutType::Twist);


data Pastry
  = Donut DonutType
  | Croissant
  | CinnamonRoll
  deriving (Show)

let twistDonut = Donut Twist

```

#### Partial field accessors

Haskell allows partial field accessors, while Rust does not.

For example, let’s take `Croissant`: the `price` field is present in both constructors, while `filling` is present only in `WithFilling`. In Rust, we get a compilation error when we try to access the filling of a plain croissant. In Haskell, we get a runtime error.

```
enum Croissant {
    Plain { price: f64 },
    WithFilling { filling: String, price: f64 },
}

let plain = Croissant::Plain { price: 1.75 };

println!("{}", plain.price)
// ^^^^^
// error[E0609]: no field `price` on type `Croissant`


data Croissant
  = Plain {price :: Double}
  | WithFilling {filling :: String, price :: Double}

let plain = Plain 1.75

-- This is ok and works as expected:
print $ price plain
-- Prints: 1.75

-- This is not
print $ filling plain
-- runtime error: No match in record selector filling

```

### Pattern matching

We could have used pattern matching to deconstruct values in the previous snippets. To illustrate this, let’s create a function that returns a receipt for a croissant.

```
fn to_receipt(croissant: Croissant) -> String {
    match croissant {
        Croissant::Plain { price } => format!("Plain croissant: ${price}"),
        Croissant::WithFilling { filling: _, price } => {
            format!("Croissant with filling: ${}", price)
        }
    }
}

let croissant = Croissant::WithFilling {
    filling: "Ham & Cheese".to_string(),
    price: 3.35,
};
println!("{:?}", to_receipt(croissant));
// Prints: "Croissant with filling: $3.35"


toReceipt :: Croissant -> String
toReceipt croissant = case croissant of
  Plain price -> "Plain croissant: $" <> show price
  WithFilling _ price -> "Croissant with filling: $" <> show price

print $ toReceipt $ WithFilling "Ham & Cheese" 3.35
-- Prints: "Croissant with filling: $3.35"

```

Haskell also allows an alternative syntax:

```
toReceipt :: Croissant -> String
toReceipt (Plain price) = "Plain croissant: $" <> show price
toReceipt (WithFilling _ price) = "Croissant with filling: $" <> show price

```

#### Partial patterns

Rust is strict about pattern matches being complete.

```
fn to_receipt(croissant: Croissant) -> String {
    match croissant {
// ^^^^^^^^^
        Croissant::Plain { price } => format!("Plain croissant: ${}", price),
    }
}
// error[E0004]: non-exhaustive patterns:
// pattern `Croissant::WithFilling { .. }` not covered

```

While Haskell allows partial pattern matches:

```
toReceipt :: Croissant -> String
toReceipt croissant = case croissant of
  (PlainCroissant price) -> "Plain croissant: $" <> show price

print $ toReceipt $ WithFilling "Ham & Cheese" 3.35
-- runtime error: Non-exhaustive patterns in case

```

But it’s highly discouraged, and a warning can catch this at compile time.

```
Pattern match(es) are non-exhaustive
In a case alternative:
  Patterns of type ‘Croissant’ not matched: WithFilling _ _

```

## Failure handling

Now, let’s look at two commonly used ADTs: `Option` / `Maybe` and `Result` / `Either`, as well as the standard ways of dealing with errors.

### `Option` / `Maybe` and `Result` / `Either`

`Option` has two variants: `None` or `Some`; `Maybe`: `Nothing` or `Just`.

```
// Defined in the standard library
enum Option<T> {
    None,
    Some(T),
}

let head = ["Donut", "Cake", "Cinnamon roll"].get(0);
println!("{:?}", head);
// Prints: Some("Donut")

let no_head: Option<&i32> = [].get(0);
println!("{:?}", no_head);
// Prints: None


-- Defined in the standard library
data Maybe a = Just a | Nothing

safeHead :: [a] -> Maybe a
safeHead (x : _) = Just x
safeHead [] = Nothing

print $ safeHead ["Donut", "Cake", "Cinnamon roll"]
-- Prints: Just "Donut"

print $ safeHead []
-- Prints: Nothing

```

`Result` also has two variants: `Ok` or `Err`; `Either`: `Right` and `Left` (by convention, `Right` is success and `Left` is failure).

```
// Defined in the standard library
enum Result<T, E> {
    Ok(T),
    Err(E),
}

#[derive(Debug)]
struct DivideByZero;

fn safe_division(x: i32, y: i32) -> Result<i32, DivideByZero> {
    match y {
        0 => Err(DivideByZero),
        _ => Ok(x / y),
    }
}

println!("{:?}", safe_division(4, 2));
// Prints: Ok(2)

println!("{:?}", safe_division(4, 0))
// Prints: Err(DivideByZero)


-- Defined in the standard library
data Either a b = Left a | Right b
data DivideByZero = DivideByZero
  deriving (Show)

safeDivision :: Int -> Int -> Either DivideByZero Int
safeDivision x y = case y of
  0 -> Left DivideByZero
  _ -> Right $ x `div` y

print $ safeDivision 4 2
// Prints: Right 2

print $ safeDivision 4 0
// Prints: Left DivideByZero

```

When we work with either of the types, it’s common to pattern match to deal with different cases. Because it can be tiresome, both languages provide alternatives. Let’s check them out first.

In Rust, we can get a value from an `Option` or a `Result` by calling `unwrap`.

```
println!("{:?}", safe_division(4, 2).unwrap());
// Prints: 2

println!("{:?}", safe_division(4, 0).unwrap());
// thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: DivideByZero'

```

Calling it on a `None` or `Error` will [panic](https://doc.rust-lang.org/std/macro.panic.html) the program, defeating the purpose of error handling.

* * *

**💡 What happens when a panic occurs?**

By default, panics will print a failure message, unwind, clean up the stack, and abort the process. You can also configure Rust to display the call stack.

* * *

We can use `unwrap_or()` to safely unwrap values.

```
let item = [].get(0).unwrap_or(&"Plain donut");

println!("I got: {}", item);
// Prints: I got: Plain donut

```

* * *

💡 There are a couple of ways to safely unwrap values:

- `unwrap_or()`, which eagerly evaluates the default value;
- `unwrap_or_else()`, which lazily evaluates the default value;
- `unwrap_or_default()`, which relies on the type’s `Default` trait implementation.

* * *

It’s not very idiomatic to get things out of things in Haskell, but the standard library provides a similar function called `fromMaybe`.

```
import Data.Maybe (fromMaybe)

let item = fromMaybe "Plain donut" $ safeHead []

print $ "I got: " ++ item
-- Prints: I got: Plain donut

```

We prefer to chain things together in Haskell.

We can chain multiple enums and operations in Rust using the `and_then()` method. For example, we can sequence a few safe divisions:

```
let eighth = safe_division(128, 2)
    .and_then(|x| safe_division(x, 2))
    .and_then(|x| safe_division(x, 2));

println!("{:?}", eighth); 
// Prints: Ok(16)

let failure = safe_division(128, 0).and_then(|x| safe_division(x, 2));
println!("{:?}", failure);
// Prints: Err(DivideByZero)

```

* * *

💡 These are lambdas (we’ll cover them in detail later):

```
|x| x + 2


\x -> x + 2

```

* * *

In Haskell, we use the bind (`>>=`) operator.

```
let eighth = 
      safeDivision 128 2 >>= \x ->
          safeDivision x 2 >>= \x ->
            safeDivision x 2

print eighth -- Prints: Right 16

let failure = safeDivision 128 0 >>= \x -> safeDivision x 2
print failure
-- Prints: Left DivideByZero

```

And last but not least, Rust provides the question mark operator (`?`) to deal with `Result` and `Option`.

```
use std::collections::HashMap;

let prices = HashMap::from([("Cake", 1.2), ("Donut", 1.0), ("Cinnamon roll", 2.25)]);

fn order_sweets(prices: HashMap<&str, f64>) -> Option<f64> {
    let donut_price = prices.get("Donut")?; // early return if None
    let cake_price = prices.get("Cake")?; // early return if None
    Some(donut_price + cake_price)
}

let total_price = order_sweets(prices);
println!("{:?}", total_price); 
// Prints: Some(2.2) 

```

The operation short-circuits in case of failure. For example, if we try to look up and use a non-existing item:

```
fn order_sweets(prices: HashMap<&str, f64>) -> Option<f64> {
    let donut_price = prices.get("Donut")?;
    let cake_price = prices.get("Cake")?;
    let missing_price = prices.get("Something random")?; // Fails here

    Some(donut_price + cake_price + missing_price)
}

let total_price = order_sweets(prices);
println!("{:?}", total_price); 
// Prints: None

```

And in Haskell, we favor do-notation (syntactic sugar to chain special expressions; we are big fans of these).

```
import Data.HashMap.Strict (HashMap)
import qualified Data.HashMap.Strict as HashMap

let prices = HashMap.fromList [("Donut", 1.0), ("Cake", 1.2), ("Cinnamon roll", 2.25)]
 
orderSweets :: HashMap String Double -> Maybe Double
orderSweets prices = do
  donutPrice <- HashMap.lookup "Donut" prices -- early return if Nothing
  cakePrice <- HashMap.lookup "Cake" prices -- early return if Nothing
  Just $ donutPrice + cakePrice

let totalPrice = orderSweets prices
print totalPrice
-- Prints: Just 2.2

```

And as expected, if one lookup fails, the whole function fails:

```
orderSweets :: HashMap String Double -> Maybe Double
orderSweets prices = do
  donutPrice <- HashMap.lookup "Donut" prices
  cakePrice <- HashMap.lookup "Cake" prices
  missingPrice <- HashMap.lookup "Something random" prices -- Fails here
  Just $ donutPrice + cakePrice + missingPrice

let totalPrice = orderSweets prices
print totalPrice
-- Prints: Nothing

```

### General failure philosophy

We can split errors into two categories:

- recoverable errors or regular errors (such as a “file not found” error);
- unrecoverable errors or programmer mistakes (aka bugs, such as the “dividing by zero” error).

As we review in the previous section, the first category can be covered by enums. It’s the errors that we either want to handle, report to the user, or retry the operation that caused them.

Let’s look into unrecoverable errors. Rust has a `panic!` macro that stops program execution in case of an unrecoverable error.

```
// Hey users, don't forget to check for 0 yourselves!
fn optimistic_division(x: i32, y: i32) -> i32 {
    match y {
        0 => panic!("This should never happen"),
        _ => x / y,
    }
}

optimistic_division(2, 0);
// thread 'main' panicked at 'This should never happen' ...

```

Even though it’s possible, code like this is generally discouraged in Rust.

Haskell developers painstakingly try to use types to avoid the need for errors in the first place. If you get approvals from at least two other developers and CTO, you can use `error` (or `impureThrow`):

```
-- Hey users, don't forget to check for 0 yourselves!
optimisticDivision :: Int -> Int -> Int
optimisticDivision _ 0 = error "This should never happen"
optimisticDivision x y = x `div` y

optimisticDivision 2 0
-- error: This should never happen
-- CallStack (from HasCallStack): error, called at ...

```

But let’s be honest: we’re all prone to think that we’re smarter than the compiler, which leads to an occasional “this should never happen” error message in the logs. Both standard libraries expose functions that can panic/error (for instance, accessing elements of the standard vector/list by index).

* * *

💡 Haskell adds another dimension to failure handling by distinguishing pure functions from potentially impure ones. We’ll cover this later in the article.

* * *

## Polymorphism

Most polymorphism falls into one of two broad categories: parametric polymorphism (same behavior for different types) and ad-hoc polymorphism (different behavior for different types).

### Parametric polymorphism

Rust supports two types of generic code:

- compile-time generics, similar to C++ templates;
- run-time generics, similar to virtual functions in C++ and generics in Java.

Using [compile-time generics](https://serokell.io/blog/rust-generics) (which we just call generics) is similar to using types with type variables in Haskell. For example, we can write a generic function that reverses any type of vector.

```
// [1] [1]
fn reverse<T>(vector: &mut Vec<T>) {
    let mut new_vector = Vec::new();

    while let Some(last) = vector.pop() {
        new_vector.push(last);
    }

    *vector = new_vector;
}

// [1]: `T` is a type parameter.
// The function is generic over the type `T`.
// That means that `T` can be of any type.

```

* * *

💡 We can use any identifier to signify a type parameter in Rust.

It’s common to name generic types starting from the `T` and continuing alphabetically. Sometimes the names can be more meaningful, for example, the types of keys and values: `HashMap<K, V>`.

* * *

The `reverse` function iterates over the elements of a vector using the `while` loop. We get the elements in the reverse order because `pop` removes the last element from a vector and returns it. We can use this function with any vector:

```
let mut price_vector: Vec<f64> = vec![1.0, 1.2, 2.25];
reverse(&mut price_vector);
println!("{:?}", price_vector);
// Prints: [2.25, 1.2, 1.0]

let mut items_vector: Vec<&str> = vec!["Donut", "Cake", "Cinnamon roll"];
reverse(&mut items_vector);
println!("{:?}", items_vector);
// Prints: ["Cinnamon roll", "Cake", "Donut"]

```

We can write a similar function for Haskell lists. Note that the Rust function reverses the vector in place, while the Haskell function returns a new list.

```
-- [1]
reverseList :: [a] -> [a]
reverseList = go []
 where
  go accumulator [] = accumulator
  go accumulator (current : rest) = go (current : accumulator) rest

-- [1]: `a` is a type variable.
-- That means that `a` can be of any type.

```

We use the intermediate `go` function to recursively go over the original list and accumulate its elements in reverse order.

* * *

💡 Type variable names in Haskell start with a lowercase letter.

Conventions:

- Ordinary type variables with no particular meaning: `a`, `b`, `c`, etc.
- Errors: `e`.
- Famous typeclasses: `f` (functors, applicatives), `m` (monads), etc.

* * *

We can use this function with any list:

```
let priceList = [1.0, 1.2, 2.25]
print $ reverseList priceList
-- Prints: [2.25, 1.2, 1.0]

let itemsList = ["Donut", "Cake", "Cinnamon roll"]
print $ reverseList itemsList
-- Prints: ["Cinnamon roll", "Cake", "Donut"]

```

### Ad-hoc polymorphism

[Rust traits](https://serokell.io/blog/rust-traits) and [Haskell typeclasses](https://serokell.io/blog/haskell-typeclasses) are siblings – their methods can have different implementations for different types.

We’ve already seen (and even derived) one standard trait and typeclass: `Debug` and `Show`, which allow us to convert types to strings for debugging. Let’s derive and use another one for comparing values.

```
#[derive(Debug, PartialEq)]
struct Item {
    name: String,
    price: f64,
}

let donut = Item {
    name: "Donut".to_string(),
    price: 1.0,
};

let cake = Item {
    name: "Cake".to_string(),
    price: 1.2,
};

println!("{}", donut == cake); 
// Prints: false

println!("{}", donut == donut); 
// Prints: true

```

* * *

**💡`PartialEq` and `Eq`.**

We can’t derive `Eq` for `Item` in Rust because `Eq` is not implemented for `f64` (because `NaN != NaN`).

* * *

```
data Item = Item
  { name :: String
  , price :: Double
  }
  deriving (Show, Eq)

let donut = Item "Donut" 1.0
let cake = Item "Cake" 1.2

print $ donut == cake
-- Prints: False

print $ donut == donut
-- Prints: True

```

It shouldn’t be surprising that we can also define our own typeclasses and traits. For example, let’s create one to tax data.

```
trait Taxable {
    fn tax(&self) -> f64;
}

// It's 10% of the price
impl Taxable for Item {
    fn tax(&self) -> f64 {
        self.price * 0.1
    }
}

println!("{}", donut.tax());
// Prints: 0.1

```

In Rust, we **implement** the `Taxable` trait for the `Item` struct; in Haskell, we create an **instance** of the `Taxable` typeclass for the `Item` datatype.

* * *

💡 `tax` is an instance method – a trait method that requires an instance of the implementing type via the `&self` argument. The `self` is an instance of the implementing type that gives us access to its internals.

* * *

```
class Taxable a where
  tax :: a -> Double

-- It's 10% of the price
instance Taxable Item where
  tax (Item _ price) = price * 0.1

print $ tax donut
-- Prints: 0.1

```

We can implement functions that rely on typeclasses and traits. Such as a function that returns a sum of taxes for a collection of taxable elements.

```
fn tax_all<T: Taxable>(items: Vec<T>) -> f64 {
    items.iter().map(|x| x.tax()).sum()
}

println!("{}", tax_all(vec![donut, cake])); 
// Prints: 0.22

```

In Rust, we use trait bounds to restrict generics: `<T: Taxable>` declares a generic type parameter with a trait bound. In Haskell, we use (typeclass) constraints to restrict type variables; in the following snippet, it’s `Taxable a`.

```
taxAll :: Taxable a => [a] -> Double
taxAll items = sum $ map tax items

print $ taxAll [donut, cake]
-- Prints: 0.22

```

* * *

💡 We can use multiple constraints in both languages:

```
fn tax_everything<T: Taxable, U: Taxable + Eq>(items: Vec<T>, special: U) -> f64


taxEverything :: (Taxable a, Taxable b, Eq b) => [a] -> b -> Double

```

* * *

On the surface, these mechanisms are quite similar, but there are some differences. Rust doesn’t allow orphan instances – a trait implementation must appear in the same crate (package) as either the type or trait definition. This prevents different crates from independently defining conflicting implementations (instances).

In Haskell, we should define instances in the same module where the type is declared or in the module where the typeclass is. Otherwise, Haskell emits a warning.

Also, Rust refuses to accept code with overlapping (ambiguous duplicate) instances. At the same time, Haskell allows several instances that apply to the same type – compilation fails only when we try to use an ambiguous instance.

* * *

💡 **Fun fact:** Haskell also has a mechanism called [Generics](https://wiki.haskell.org/GHC.Generics).

The [`Generic`](https://hackage.haskell.org/package/base-4.17.0.0/docs/GHC-Generics.html#t:Generic) typeclass allows us to define polymorphic functions for a variety of data types based on their generic representations – we can ignore the actual datatypes and work with their “shapes” or “structure” (that consists, for example, of constructors and fields).

* * *

## Advanced topics

We can’t go too deep on these topics because each deserves more attention, but we’ll provide basic comparisons and pointers for further learning.

### Metaprogramming

The next abstraction level is compile-time metaprogramming, which helps generate boilerplate code and is represented by macros and Template Haskell.

Macros in Rust are built into the language. They come in two different flavors: declarative and procedural macros (which cover function-like macros, custom derives, and custom attributes). Declarative macros are the most widely used; they are referred to as “macros by example” or, more often, as plain “macros”.

Let’s use them to make pairs.

```
macro_rules! pair {
    ($x:expr) => {
        ($x, $x)
    };
}

```

The syntax for calling macros looks almost the same as for calling functions.

* * *

💡 We’ve been using the `println!` macro throughout the article.

* * *

```
let pair_ints: (i32, i32) = pair!(1);
println!("{:?}", pair_ints);
// Prints: (1, 1)

let pair_strs: (&str, &str) = pair!("two");
println!("{:?}", pair_strs);
// Prints: ("two", "two")

```

Template Haskell, also known as TH, is a language extension; the [`template-haskell`](https://hackage.haskell.org/package/template-haskell) package exposes a set of functions and datatypes for it.

Let’s use it to make pairs.

```
import Language.Haskell.TH

pair :: ExpQ
pair = [e|\x -> (x, x)|]

```

We have to define it in another module because top-level splices, quasi-quotes, and annotations must be imported, not defined locally. To use Template Haskell, we have to enable the [`TemplateHaskell`](https://downloads.haskell.org/~ghc/9.2.1/docs/html/users_guide/exts/template_haskell.html#extension-TemplateHaskell) extension.

```
{-# LANGUAGE TemplateHaskell #-}

let pairInts :: (Int, Int) = $pair 1
print pairInts
-- Prints: (1, 1)

let pairStrs :: (String, String) = $pair "two"
print pairStrs
-- Prints: ("two", "two")

```

Template Haskell doesn’t have a spotless reputation: it introduces extra syntax, increases complexity, and in the past, considerably affected compilation speed (it’s way better these days). Some people prefer to use alternative methods of reducing boilerplate, such as [Generics](https://wiki.haskell.org/GHC.Generics) ([`Generic`](https://hackage.haskell.org/package/base-4.17.0.0/docs/GHC-Generics.html#t:Generic) typeclass).

* * *

💡 Later versions of GHC support [typed Template Haskell](https://serokell.io/blog/typed-template-haskell-overview), which type-checks the expressions at their definition site rather than at their usage (like regular TH).

* * *

### Runtime and concurrency

Rust has no built-in runtime (scheduler). The standard library allows using OS threads directly through [`std::thread`](https://doc.rust-lang.org/std/thread/). For example, we can spawn a thread:

```
use std::thread;
use std::time::Duration;

thread::spawn(|| {
    thread::sleep(Duration::from_secs(1));
    println!("Hello from the spawned thread!");
});
println!(“Spawned a thread”);

thread::sleep(Duration::from_secs(2));
println!("Hello from the main thread!");

```

The Rust language defines `async/await` but no concrete execution strategy.

```
#[derive(Debug)]
struct Donut;

async fn order_donut() -> Donut {
    println!("Ordering a donut");
    Donut
}

async fn eat(donut: Donut) {
    println!("Eating a donut {:?}", donut)
}

async fn order_and_consume() {
    // Wait for the order before consuming it.
    // `await` doesn't block the thread
    let donut = order_donut().await;
    eat(donut).await;
}

// Blocks the current thread until the future is completed
use futures::executor::block_on;
block_on(order_and_consume());

```

We use the standard [`futures`](https://docs.rs/futures/), which has its own executor but is not a full runtime. To get an asynchronous runtime in Rust, we have to use external libraries, such as [`Tokio`](https://tokio.rs/) and [`async-std`](https://async.rs/).

Here is a snippet of code that uses `Tokio` to do some concurrent work using three different services and then either collect the successful result or return a timeout error (all the other errors are ignored for simplicity):

```
use tokio::time::timeout;

// Imagine `do_work` does some meaningful work
// async fn do_work(work: &Work, service: &Service) -> String

async fn concurrent_program(work: &Work) -> Result {
    timeout(Duration::from_secs(2), async {
        tokio::join!(
            do_work(work, &Service::ServiceA),
            do_work(work, &Service::ServiceB),
            do_work(work, &Service::ServiceC)
        )
    })
    .await
    .map(|(a, b, c)| Result::Success(vec![a, b, c]))
    .unwrap_or_else(|_| Result::Timeout)
}

// Still have to run this program
concurrent_program(&some_work).await;

```

_Note that this will run all the work concurrently on the same task. If we want to do the job simultaneously or in parallel, we need to call `tokio::spawn` for each of the tasks to spawn and run it._

* * *

💡 Rust doesn’t oblige you to stick to one type of concurrency model (such as threads, async, etc.). You can use any if you find a suitable crate (library).

* * *

Haskell has green threads – the runtime system manages threads (instead of directly using native OS threads). The essential operation is forking a thread with `forkIO`:

```
import Control.Concurrent (threadDelay, forkIO)

forkIO $ do
  threadDelay $ 1000 * 1000
  print "Hello from the spawned thread!"

print "Spawned a thread"

threadDelay $ 2 * 1000 * 1000
print "Hello from the main thread!"

```

Various libraries in Haskell take advantage of green threads to provide powerful and composable APIs. For example, the [`async`](https://hackage.haskell.org/package/async) package (library). The following snippet uses `async` to do concurrent work with three services and then assembles the result.

```
import Control.Concurrent (threadDelay)
import Control.Concurrent.Async (mapConcurrently)
import System.Timeout (timeout)

-- Imagine `doWork` does some meaningful work
-- doWork :: Work -> Service -> IO PartialResult

concurrentProgram :: Work -> IO Result
concurrentProgram work = do
  results <-
    timeout twoSeconds $
      mapConcurrently (doWork work) [ServiceA, ServiceB, ServiceC]
  pure $ case results of
    Just success -> Success success
    Nothing -> Timeout
 where
  twoSeconds = 1000 * 1000

-- Run the program
concurrentProgram someWork

```

And we have to mention the [Software Transactional Memory (STM)](https://hackage.haskell.org/package/stm) abstraction in Haskell, which allows us to group multiple state-changing operations and perform them as a single atomic operation.

Imagine the situation: I have $0 and want to buy a donut for $3; also, I earn $1 per second. I can keep trying to buy a donut until I have sufficient funds. We have a typical money transfer transaction, which needs to be retried and can be simulated using STM. We don’t need to worry about transaction rollbacks or retries – STM handles all of these for us.

```
runSimulation :: IO ()
runSimulation = do
  -- Start with 0
  wallet <- newTVarIO 0
  cashRegister <- newTVarIO 100

  -- Get paid at the same time
  _ <- forkIO $ getPaid wallet

  -- Try to make a donut-money transaction
  atomically $ do
    myCash <- readTVar wallet
    -- Check if I have enough money
    check $ myCash >= donutPrice
    -- Subtract the amount from my wallet
    writeTVar wallet (myCash - donutPrice)
    storeCash <- readTVar cashRegister
    -- Add the amount to the cash register
    writeTVar cashRegister (storeCash + donutPrice)

  myFinal <- readTVarIO wallet
  print $ "I have: $" ++ show myFinal

  storeFinal <- readTVarIO cashRegister
  print $ "Cash register: $" ++ show storeFinal
where
  donutPrice = 3

-- Put $1 in my wallet every second
getPaid :: TVar Int -> IO ()
getPaid wallet = forever $ do
  threadDelay $ 1000 * 1000
  atomically $ modifyTVar wallet (+ 1)
  print "I earned a dollar"

```

* * *

💡 [`TVar`](https://hackage.haskell.org/package/stm-2.5.1.0/docs/Control-Concurrent-STM-TVar.html) stands for transactional variable, which we can read or write to within STM, using operations such as [`readTVar`](https://hackage.haskell.org/package/stm-2.5.1.0/docs/Control-Concurrent-STM-TVar.html#v:readTVar) and [`writeTVar`](https://hackage.haskell.org/package/stm-2.5.1.0/docs/Control-Concurrent-STM-TVar.html#v:writeTVar). We can perform a computation in the STM using the [`atomically`](https://hackage.haskell.org/package/stm-2.5.1.0/docs/Control-Monad-STM.html#v:atomically) function.

* * *

When we run the program, we see that I must be paid at least three times before the transaction succeeds:

```
runSimulation
-- Prints: 
-- "I earned a dollar"
-- "I earned a dollar"
-- "I earned a dollar"
-- "I have: $0"
-- "Cash register: $103"

```

## Functions and functional programming

Functions are widespread in both languages. Haskell and Rust support higher-order functions. Haskell is always functional, and functional Rust is frequently just proper, idiomatic Rust.

### Lambdas and closures

A lambda is an anonymous function that can be treated like a value. We can use lambdas as arguments for higher-order functions.

In Rust:

```
let bump = |i: f64| i + 1.0;
println!("{}", bump(1.2));
// Prints: 2.2

let bump_inferred = |i| i + 1.0;
println!("{}", bump_inferred(1.2));
// Prints: 2.2

```

In Haskell:

```
let bump = \(i :: Double) -> i + 1.0
print $ bump 1.2
-- Prints: 2.2

let bumpInferred = \i -> i + 1.0
print $ bumpInferred 1.2
-- Prints: 2.2

```

* * *

💡 Note that annotating a type variable might require the [`ScopedTypeVariables`](https://downloads.haskell.org/ghc/latest/docs/users_guide/exts/scoped_type_variables.html#extension-ScopedTypeVariables) extension, depending on your usage. But also, writing lambdas like that can be redundant in Haskell. We could have written a normal function:

```
let bump :: Double -> Double
    bump i = i + 1.0

```

* * *

We can use closures to capture values from the scope/environment in which they’re defined. For example, we can define a simple closure that captures the `outside` variable:

```
|x| x + outside


\x -> x + outside

```

In Rust, each value has a lifetime. Because closures capture environment values, we have to deal with their ownership and lifetimes. For example, let’s write a function that returns a greeting function:

```
fn create_greeting() -> impl Fn(&str) -> String {
    let greet = "Hello,";
    move |name: &str| format!("{greet} {name}!")
}

let greeting_function = create_greeting();

println!("{}", greeting_function("Rust"));
// Prints: Hello, Rust!

```

We use `move` to force the closure to take ownership of the `greet` variable.

* * *

💡 If we forget to use `move`, we get an error.



```
error[E0373]: closure may outlive the current function, but it borrows `greet`, which is owned by the current function
   |
   | |name: &str| format!("{greet} {name}!")
   | ^^^^^^^^^^^^ ----- `greet` is borrowed here
   | |
   | may outlive borrowed value `greet`
   |
note: closure is returned here
   |
   | |name: &str| format!("{greet} {name}!")
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
help: to force the closure to take ownership of `greet` (and any other referenced variables), use the `move` keyword
   |
   | move |name: &str| format!("{greet} {name}!")
   | ++++

```

* * *

**💡 What is `Fn`?**

How a closure captures and handles values from the environment determines which traits (one, two, or all three) it implements and how the closure can be used. There are three traits:

- [`FnOnce`](https://doc.rust-lang.org/stable/std/ops/trait.FnOnce.html) (can be called once);
- [`FnMut`](https://doc.rust-lang.org/stable/std/ops/trait.FnMut.html) (can be called more than once, may mutate state);
- [`Fn`](https://doc.rust-lang.org/stable/std/ops/trait.Fn.html) (can be called more than once, no state mutation).

* * *

In Haskell, we don’t worry much about closures.

### Currying and partial application

Currying is converting a function that takes multiple arguments into a function that takes them one at a time. Each time we call a function, we pass it one argument, and it returns another function that also takes one argument until all arguments are passed.

In Haskell, all functions are considered curried, which might not be obvious because it’s hidden in the syntax: `Double -> Double -> Double` is actually `Double -> (Double -> Double)` and `add x y` is actually `(add x) y`.

```
add :: Double -> Double -> Double
add x y = x + y

bump :: Double -> Double 
bump = add 1.0

full :: Double 
full = add 1.0 1.2

```

When we pass `1.0` to `add`, we get another function, `bump`, which takes a double, adds 1 to it, and returns that sum as a result.

```
add x y = x + y
bump = add 1.0

print $ bump 1.2
-- Prints: 2.2

```

`bump` is the result of partial application, which means we pass less than the total number of arguments to a function that takes multiple arguments.

We can do this in Rust, but it’s not idiomatic.

```
fn add(x: f64) -> impl Fn(f64) -> f64 {
    move |y| x + y
}

let bump = add(1.0);

println!("{}", bump(1.2)); 
// Prints: 2.2

```

There are crates that make it easier like [partial\_application](https://crates.io/crates/partial_application):

```
use partial_application::partial;

fn add(x: f64, y: f64) -> f64 {
    x + y
}

let bump2 = partial!(add => 1.0, _);

println!("{}", bump(1.2)); 
// Prints: 2.2

```

Currying and partial application are convenient when we pass functions around as values and allows us to use neat features, such as composition.

### Function composition

In Haskell, we can use function composition, which pipes the result of one function to the input of another, creating an entirely new function.

* * *

💡 We use the dot operator (`.`) to implement function composition in Haskell.

* * *

For example, we can compose three functions to get a maximum price from the list and negate it.

```
import qualified Data.HashMap.Strict as HashMap

-- HashMap.elems :: HashMap String Double -> [Double]
-- maximum :: [Double] -> Double
-- negate :: Double -> Double

negativeMaxPrice :: HashMap String Double -> Double
negativeMaxPrice = negate . maximum . HashMap.elems

let prices = HashMap.fromList [("Donut", 1.0), ("Cake", 1.2)]
print $ negativeMaxPrice prices 
-- Prints: -1.2

```

We can technically do it in Rust, but it’s not commonly used and not part of the standard library.

### Iterators

In Rust, the iterator pattern allows us to traverse collections. Iterators are responsible for the logic of iterating over each item and determining when the sequence has finished.

For example, let’s use iterators to get the total price of all the products except for donuts:

```
use std::collections::HashMap;

let prices = 
  HashMap::from([("Donut", 1.0), ("Cake", 1.2), ("Cinnamon roll", 2.25)]);

let total: f64 = prices
    .into_iter()
    .filter(|&(name, _)| name != "Donut")
    .map(|(_, price)| price)
    .sum();

println!("{}", total); 
// Prints: 3.45

```

Iterators are lazy — they don’t do anything unless they are consumed. We can also use [`collect`](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.collect) to transform an iterator into another collection. For example, we can return the prices instead:

```
use std::collections::HashMap;

let prices = 
  HashMap::from([("Donut", 1.0), ("Cake", 1.2), ("Cinnamon roll", 2.25)]);

let other_prices: Vec<f64> = prices
    .into_iter()
    .filter(|&(name, _)| name != "Donut")
    .map(|(_, price)| price)
    .collect();

println!("{:?}", other_prices);
// Prints: [1.2, 2.25]

```

In Haskell, we work with collections directly.

```
import qualified Data.HashMap.Strict as HashMap

let prices = 
      HashMap.fromList [("Donut", 1.0), ("Cake", 1.2), ("Cinnamon roll", 2.25)]

let total =
	    sum
	      $ HashMap.elems
	      $ HashMap.filterWithKey (\name _ -> name /= "Donut")
	      $ prices

print total 
-- Prints: 3.45


import qualified Data.HashMap.Strict as HashMap

let prices = 
      HashMap.fromList [("Donut", 1.0), ("Cake", 1.2), ("Cinnamon roll", 2.25)]

let otherPrices =
      HashMap.elems $
        HashMap.filterWithKey (\name _ -> name /= "Donut") prices

print otherPrices 
-- Prints: [1.2, 2.25]

```

### Associated functions and methods

In Rust, we can connect functions to particular types — via associated functions or methods. Associated functions are called on the type, and methods are called on a particular instance of a type.

```
struct Item {
    name: String,
    price: f64,
}

impl Item {
    fn free(name: String) -> Item {
        Item { name, price: 0.0 }
    }

    fn print_receipt(&self) {
        println!("{}: ${}", self.name, self.price);
    }
}

// Calling an associated function:
let free_donut = Item::free("Regular donut".to_string());

// Calling a method:
free_donut.print_receipt(); 
// Prints: Regular donut: $0

```

In Haskell, we can’t and don’t.

## Things we worry about in Rust

Speaking of the things we don’t do in Haskell. It shouldn’t be a surprise that the feature that sets Rust apart is its ownership model and the borrow checker.

Remember how we used `move` to force the closure to take ownership of the variable?

```
fn create_greeting() -> impl Fn(&str) -> String {
    let greet = "Hello,";
    move |name: &str| format!("{greet} {name}!")
}

```

Rust is a statically memory-managed language, which requires us to think about the lifetimes of values.

* * *

💡 If you want to learn more about this topic, check out the [Understanding Ownership](https://doc.rust-lang.org/book/ch04-00-understanding-ownership.html#understanding-ownership) chapter of the Rust book.

* * *

And in Haskell? In Haskell, we have space leaks (but we usually don’t worry about those). 😉

## Things we worry about in Haskell

### Laziness

Haskell programs are executed using lazy evaluation, which doesn’t perform a computation until its result is required and avoids unnecessary computation.

Laziness encourages programming in a modular style without worrying about intermediate data and allows working with infinite structures. Let’s illustrate this by writing a function that returns the first prime number larger than `10 000`. We can start with an infinite list of naturals, filter the prime numbers, and take the first prime after `10 000`.

```
naturals :: [Int]
naturals = [1 ..]

-- Check there is no divisors for `n`
isPrime :: Int -> Bool
isPrime n = null [number | number <- [2 .. n - 1], n `mod` number == 0]

print $ take 1 $ filter (> 10000) $ filter isPrime naturals
-- Prints: [1000003]

```

Because of laziness, the work stops right after it finds the correct prime number – no need to calculate the rest of the list or the rest of the primes.

This computation takes `~40MB` of memory _(YMMV)_, primarily the runtime overhead. If we increase the number to `100 000`, the memory usage stays the same.

All good so far. What if we try something else? Let’s take `1 000 000` naturals and return their sum along with the length of the list.

```
let nums = take 10000000 naturals
print $ (sum nums, length nums)
-- (500000500000,1000000)

```

This computation takes `~129MB` of memory _(YMMV)_. And if we take `10 000 000` – the memory usage goes up to `~1.376GB`. Oops.

* * *

**💡 Why does it happen?**

Because the `nums` list is used for both `sum` and `length` computations, the compiler can’t discard list elements until it evaluates both.

Note that `sum $ take 10000000 naturals` runs in constant memory.

* * *

💡 If you want to learn more, check out [our videos on laziness](https://www.youtube.com/watch?v=DSy7bscJL7k).

* * *

So, it’s favorable to be mindful of how expressions are evaluated when working with data in Haskell to avoid space leaks.

### Purity

Haskell is pure – invoking a function with the same arguments always returns the same result.

As a consequence, in Haskell, there is no distinction between a zero-argument function and a constant.

```
f(x, y) // functino with two arguments
f(x) // function with one argument
f() // function with zero arguments
C // constant


f x y -- function with two arguments
f x -- function with one argument
F -- zero argument / constant 
c -– zero argument / constant

```

* * *

💡 Pure functions do not have side effects.

* * *

Purity, together with laziness, raises some challenges. Without digging too deep into the rabbit hole, let’s look at an example pseudo-Haskell code. The following impure example reads 2 integers from the standard input and returns them as a list (pay attention to the order):

```
-- `readNumber` is an impure function that reads a number from stdin

-- some impure computation
pseudoComputation =
  let first = readNumber
  let second = readNumber
  print [second, first]

```

As we’ve learned, because of laziness, Haskell doesn’t evaluate an expression until it is needed: evaluation of `first` and `second` is postponed until they are used, and when it starts forcing the list, it starts with reading the `second` number and then `first`. Which is the opposite of what we want and expect.

To deal with this mess, Haskell has `IO` – a special datatype that offers better control over its execution. `IO` is a description of a computation. When executed, it can perform arbitrary effects before returning a value of type `a`.

* * *

💡 Executing `IO` is not the same as evaluating it. Evaluating an `IO` expression is pure – it returns the same description. For example, `io1` and `io2` are guaranteed to be the same:

```
-- some IO function 
-- doSomeAction :: String -> IO ()

let variable = doSomeAction "parameter"
let io1 = (var, var)


let io2 = (doSomeAction "parameter", doSomeAction "parameter")

```

* * *

We’ve been using the `print` function to print things out; here is its type signature:

```
print :: Show a => a -> IO ()

```

We can’t print outside of `IO`. We get a compilation error if we try otherwise:

```
noGreet :: String -> String
noGreet name = print $ "Hello, " <> name
-- ^^^^^^^^^^^^^^^^^^^^^^^^^
-- Couldn't match type: IO ()
-- with: String

```

Okay, we have an `IO` function. How do we run it? We need to define the program’s `main` `IO` function – the program entry point – which the Haskell runtime will execute. Let’s look at the executable module example: it asks the user for the name, reads it, and prints the greeting.

```
module Main where 

greet :: String -> IO ()
greet name = print $ "Hello, " <> name

main :: IO ()
main = do
  print "What's your name?"
  name <- getLine
  greet name

```

* * *

💡 Remember the do-notation syntax that we used with `Maybe` and `Either`? We can use it with `IO` as well! It’s convenient to put actions together.

* * *

This differs from all the languages in which we can perform side effects anywhere and anytime.

For completeness, this is how a main module looks like in Rust:

```
fn main() {
    let two = one() + one();
    println!("Hello from main and {}", two);
}

fn one() -> i32 {
    println!("Hello from one");
    1
}

```

Note that we can execute arbitrary side effects.

### Higher-order programming

In Haskell, we prefer general solutions for common patterns. For example, do-notation is syntactic sugar for the bind operator (`>>=`), which is overloaded for a bunch of types: `Maybe`, `Either e`, `[]`, `State`, `IO`, etc.

```
do
  x <- action1
  y <- action2
  f x y


-- Desugared version:
action1 >>= \x ->
  action2 >>= \y ->
    f x y

```

As a result, we can use the notation to express many problems and deal with many use cases: optionality, non-determinism, error handling, state mutation, concurrency, etc.

```
-- Dealing with optionality: 
maybeSweets :: HashMap String Double -> Maybe Double
maybeSweets prices = do
  donutPrice <- HashMap.lookup "Donut" prices
  cakePrice <- HashMap.lookup "Cake" prices
  Just $ donutPrice + cakePrice

-- Dealing with IO:
ioSweets :: Statistics -> SweetsService -> IO Double
ioSweets statistics sweets = do
  print "Fetching sweets from external service"
  prices <- fetchPrices sweets
  updateCounters statistics prices
  pure prices

```

This is one of the design patterns for structuring code in Haskell. We implement them using typeclasses, and they are supported by laws (a whole different topic). When Haskell developers see that the library provides a datatype or an interface related to one of these typeclasses, they have some expectations and guarantees about its behavior.

* * *

💡 Top 7 useful typeclasses everyone should know about:

`Semigroup`, `Monoid`, `Functor`, `Applicative`, `Monad`, `Foldable`, and `Traversable`.

* * *

Higher-kinded types are the basis for these typeclasses. A higher-kinded type is a type that abstracts over some polymorphic type. Take, for instance, `f` in the following `Functor` definition:

```
class Functor f where
  fmap :: (a -> b) -> f a -> f b

```

`f` can be `Option`, `[]`, `IO`, etc.; while `f a` can be `Option Int`, `[String]`, `IO Bool`, etc.

You can check out [Kinds and Higher-Kinded Types](https://serokell.io/blog/kinds-and-hkts-in-haskell) for a more detailed explanation.

* * *

Rust doesn’t support higher-kinded types (yet), and it’s more complex to implement concepts such as monads and their friends. Instead, Rust provides alternative approaches to solve the problems that these typeclasses solve in Haskell:

- If you want to deal with optionality or error handling, you can use `Option`/`Result` with the `?` operator.
- If you want to deal with async code – `async`/`.await`.
- etc.

## Conclusion

Turns out Rust and Haskell have a lot in common: both languages have a lot of features and can be frustrating to learn. Luckily they share a lot of concepts, and knowledge of one language can be helpful while pursuing another.

And please remember that in both cases, the compiler has your back, so don’t forget that compilers are your friends. 😉

For more articles on Haskell and Rust, follow us on [Twitter](https://twitter.com/serokell) or subscribe to the newsletter via the form below.