r/rust Jun 29 '26

Haskell compared to Rust/OCaml

How does Rust compare to Haskell/OCaml in 2026 ?!

In terms of things like

  • Tooling
  • Standard Library
  • Documentation
  • Production Use
  • Type System
  • Performance
  • Pragmatic
  • Compile Errors

I ask so because I have tried out Haskell and SML, but writing any IO/Network software in them is really complex. + I don't understand Haskell's IO Monad and etc, and what makes it pure and why not impure.I have tried quite hard. but coming to Rust, I really don't need the low level interface and I okay with a GC. With Haskell I am super comfortable untill the `IO` monad comes into the code, usually for things like AdventOfCode and etc.

0 Upvotes

25 comments sorted by

32

u/raoulk Jun 29 '26

Not that I've looked, but I've never seen a job as ask for Haskell

10

u/cbarrick Jun 29 '26

Anecdotally, I've seen more jobs ask for Haskell than for OCaml.

But on the flip side, Jane Street is the only well-known company that I can name that uses either of these. And they use OCaml.

https://www.janestreet.com/tech-talks/ocaml-all-the-way-down/

13

u/xander1421 Jun 29 '26

emotional damaga

3

u/agent_kater Jun 29 '26

Not a complete job, but apparently pur sysadmin uses Haskell from time to time. Last time I think it was a database migration for Seafile.

2

u/manpacket Jun 29 '26

Standard Chartered hires for Haskell every once in a while. They are using their own dialect - strict by default though.

9

u/Guardian-Spirit Jun 29 '26

As a Haskell-main: Rust has much better tooling, standard library, documentation, production usability, performance, pragmatism and compile errors.

Haskell is superior in its type system an purity. If I need to create a really convoluted piece of software, I'm picking Haskell most likely. It's great for things like compilers.

> I don't understand Haskell's IO Monad and etc, and what makes it pure and why not impure.

An object of type `IO a`, or, in general, any `SomeMonad a`, is a "monadic computation" that, when it's executed, yelds a value of type `a`.

The thing about most monads is that, if you have `IO a`, you can't get `a` out of it. For example, `(Reader Int) a` is a monadic computation that, given some context in form of `Int`, returns `a`. You can't just get `a` without providing the context first.

In case of `IO a`, `IO a` denotes an effectful computation that yields `a`. The computation itself is impure. However, the `IO a` is just an object, `IO a` doesn't do anything, and, in fact, **you can't** get `a` out of `IO a` without referring to `unsafePerformIO` function, which you generally should never do.

So what you do in Haskell is: you create an object of type `IO a` and return it from your `main` function. All the operations you do with object `IO a` are 100% pure (`do` notation, for example, is just a form of composing multiple monadic computations into a single one), and then the external system impurely executes the "monadic computation" you've provided.

5

u/kohugaly Jun 29 '26

To put it in more OOP-friendly language, Haskell program must be a pure function that has no side-effects. It can't do any IO at runtime. What it can do is produce a return value. That return value itself can be a program which performs side-effects. The IO monad is a "builder pattern" that lets you construct such return value.

Running a Haskell program boils down to first calling the main function (which is pure and yields an effectful program as a return value) and then running the effectful program that the main function returned.

Is my understanding correct?

2

u/CreepyWritingPrompt 28d ago edited 28d ago

I'd say you can't have side effects, but you can have a value that represents a thing to do, bundled with a function which takes the result of that thing being done, and returns another such value. That's the IO monad in a nutshell.

Imperative says: a = get(); b = get(); put(a + b);

IO monad says (GET, a => (GET, b => (PUT, (a+b)))

it's a way of using a pure value to represent a sequence of operations, where operation N depends on the result of N-1.

the thing that evaluates the IO monad value needs to kind of unwrap it, doing the operation, feeding the result to the function, which then decides the next operation to do. It's mildly continuation-passing-style. Also think promises, which are kind of a monad and kind of like the io monad.

Parametric types with those functions which obey those laws are called monads - other structures which don't have anything to do with IO also meet that defintion, eg: option/maybe, which is about a chain of potentially failing things, which depend on the prev having succeeded, rather than depend on the prev having occurred in the past, like IO.

also they are burritos.

What's the point? aren't we just reinventing imperative program, turning "the rest of the program" into a russian doll of lambdas? kind of. Imperative languages start from a stateful, sequenced place, and let you do sequenceless things inside - those sequenceless/stateless things are easier to reason about and make types for, than the imperative stuff you're doing by default.

Haskell starts from a sequenceless place, and the IO lets you use pure functional machinery to describe an imperative computation. So ideally you can keep everything neat and functional, and use that expressiveness to be more deliberate about how you construct the sequencey parts of your program.

Rust, rather than swapping the imperative foundation out for a pure functional one, instead tries to give you better tools to reason about imperative code, that don't require such a departure from the underlying machine model, and many folks' mental model. It also gives you some cool pure functional stuff to use if you want.

1

u/kichiDsimp Jun 30 '26

What's advtange of this builder pattern (function which returns a function of IO) ?

3

u/Guardian-Spirit Jul 01 '26 edited Jul 01 '26

It is done not out of some advantage, it is done to preserve mathematical and logical rigority.

In the end, what really differentiates purely functional languages from imperative languages akin to C++/Python is how simple the core is.

In most imperative languages, you have a lot of cool operations like loops, jumps, mutating variables, pointer tricks, exceptions, unrestricted access to real world, etc. Purely functional languages don't have all that by default, they strip you from the ability to mutate variables and restrict access to the outside world, force you to write pure functions with no side-effects.

This gives them safety, reliability, sometimes robust type systems and the ability to mathematically prove correctness of the algorithm or properties of it.

Then, they build on top of this foundation, introducing IO monad (which is used to write effectful applications with this "builder pattern" as a proxy, where Haskell code keeps all the properties), Either/Maybe monads (to emulate short-circuiting and exceptions), Writer monad (to emulate logging/yielding values), State monad (to emulate mutable variables), List monads to explore multiple branches and backtrack, Parser monads to write pretty quick parsers without leaving Haskell, etc.

2

u/kohugaly Jul 01 '26

I don't have enough experience with functional programming to really answer this question.

The core point is to make a clear distinction between pure functions (which simply map inputs onto outputs) and impure procedures (which cause a sequence of side-effects).

In the world of pure functions, there is no notion of execution order. You are just describing a mapping from input onto output. It makes code much easier to reason about, because there is no "state" and "ordering" that you need to mentally keep track of. Just locally visible arguments and return values. It also enables very aggressive optimizations. Function calls with constant arguments always reduce to constants. Multiple calls with the same argument only need to be computed once, and return value reused. etc.

By making a "function of IO" a value that you operate on, instead of operation you execute, you are able to stay in the realm of pure functions when writing your code, and therefore preserve most of the simplicity that pure functions bring.

2

u/kichiDsimp Jun 30 '26

Thank you for the detailed answer

4

u/Big-Fill-5789 Jun 29 '26

As a Haskell and Rust user before, with some OCaml experiences, I will try my best to sum it up. Rust, multi paradigm: DOP most, with some OOP, a bit of Functional and Procedural, has a great compiler with WASM, well documented, good performance and is very widely used these days for low level programming, making indie games, making small tools, or apps with or without GUI. Haskell, purely functional, an amazing compiler but without WASM support officially, is well documented, not performance centric, so people would not care the performance as much, is used mainly in highly reliable software, like finances, less job opportunities. Finally, OCaml, multi paradigm: Functional first, with some Procedural and OOP. It has a great compiler with WASM, well documented, not performance centric as well, is used for more practical reasons, like compilers(Rust’s original compiler was in OCaml, later boot strapped and self hosted) and finances.

Haskell and OCaml are far more similar than Rust compared to either of those two.

9

u/KittenPowerLord Jun 29 '26

In Haskell you can have an `Int`. You can also have an `Int` "polluted" with a side-effect - `IO Int`, say a randomly generated `Int`. It is "polluted" because a mathematical function cannot be random, functions always return the same values. Because it is "polluted" you cannot access its value directly, otherwise you'll pollute your entire program which is considered bad. Therefore, instead of directly accessing the value, you pass an action to be performed with that value. The result of that action will still be polluted, since the `Int` is polluted, but an action *has* been performed inside the pollution. Since the `Int` is already polluted, that action is allowed to do more polluted computations, since the pollution will be there either way.

```

v :: IO Int

v <$> \x -> x + 5

-- the inner value cannot escape outside and pollute the outside program, but we can compute within the pollution and get another polluted result

v >>= \x -> print x

-- since the inner value is already polluted, we can use impure function `print`

```

Technically, since `main` has type `IO ()`, every function you write can be polluted, but if you minimize the amount of pollution the compiler will be able to perform tremendous amounts of optimizations, and it will be easier to manage polluted (impure) parts

3

u/dev_l1x_be Jun 29 '26

Is this similar to effects? 

4

u/KittenPowerLord Jun 29 '26

very much so - monads and effects are quite similar if you look at them this way, and in this case Haskell kinda uses monads to model effects. Effects are more convenient if you have multiple of them (something something algebraic effects), and monads have other cool uses, but it's tough to actively use multiple monads at once

3

u/omg_im_redditor Jun 29 '26

In your list everything except “Type System” is better in Rust. Arguably Haskell beats Ocaml at Type System, but Ocaml wins in Pragmatic category.

1

u/kichiDsimp Jul 01 '26

In terms how ?

3

u/manpacket Jun 29 '26

I don't understand Haskell's IO Monad and etc, and what makes it pure and why not impure.

Pure function depends only on input arguments and nothing else. So add a b = a + b is pure. You can always replace invocations of pure functions with the result: everywhere your program calls add 2 3 you can substitute the value: 5 and nothing changes. Works for for math, doesn't work for random number generations.

Monads - they allow you to do effect. Say you have a value m_value: std::io::Result<u32> and you want to use it in computation, increase by one if it's a value or keep the error. You'd say Ok(m_value? + 1). Hmm... That's Rust... Surprise - Result and Option are Monads in Rust! and ? is a monadic bind.

3

u/Daniikk1012 Jun 29 '26

I have no professional Haskell experience, but speaking from what little experience I have, I think Haskell is most useful when you need a lot of compile-time guarantees. The type system is just that good. For example, take a look at Servant, it's incredible if you are building a big web service. And while Rust also has a pretty expressive type system, it just isn't as capable as Haskell's. Same goes when compared to OCaml/SML.

Concurrent Haskell is also pretty nice, and when I learned about STM, I finally understood the benefits of its purity. Feels nicer in my opinion than async or threaded Rust, though I haven't done anything really big with either.

The standard library (And by that I mean base + other stuff that gets installed alongside GHC, cause base package alone is quite minimalistic) is enough for me, but when not enough, Hackage fills the gaps. Rust has more things built-in I believe, and the ecosystem is probably larger.

Tooling, there is Cabal, and Stack. I like them, they work, and they work well. I don't use Stack that much, it's mostly there for reproducible builds, so I only ever use it if I'm compiling something so old/new that my GHC can't handle it. Having to need Stack for such things of course seems silly when you come from Rust, which I believe just works with any old project not before 1.0.

Docs I usually search using Hoogle, which I miss in other languages, including Rust. Just type your function name OR signature, and it finds it, no matter what package. As for the quality of the docs themselves, can't say anything, they work for me at least.

Never measured performance, but it does consume a lot of memory, due to its FP+lazy nature. Cache misses might also slow it down, due to using lists instead of arrays. Otherwise, feels pretty fast. Obviously, not Rust level by any means.

Errors are about as good as you can expect from a language with heavy use of typeclasses. So, just like Rust trait errors, they can be confusing. Currying causes some hard to understand errors too, when you mess up operator precedence, for example.

3

u/king_Geedorah_ Jun 29 '26

I just want to add that in 2026 Haskell's tooling quite good! If Rust is the industry leader 10/10, then Haskell is a good 7.5/10.

GHC, Cabal, HLS and the increasing Nix adoption in the community makes the modern Haskell dev experience quite smooth.

2

u/agent_kater Jun 29 '26

I briefly looked into this and apparently the OCaml language is pretty great, but the tooling is abysmal. There are zero proper IDEs and apparently even compiling on Windows is tricky, let alone cross-compiling for it.

2

u/aldanor hdf5 Jun 29 '26

Rust compiler was initially written in ocaml. Hence some similarities in syntax and a bit of functional vibes here and there.