RAII language "more functional" than Rust?

Is there any language with RAII (destructors, Drop trait) that is "more functional" than Rust?

RAII is great for situations when we want some external resource (GPU object, pointer on remote server, ...) to get auto freed when they go out of scope / have RC count drop to 0.

RAII is also very hard to do in languages with GC.

Although I miss some of the functional features of OCaml/Haskell, RAII is really nice.

Of languages that support RAII, are any "more functional" than Rust?

If you want to write code in a more functional style than Rust will ergonomically allow, then I think you may want to look into languages with finalizers, i.e. non-deterministic object destruction happening "eventually" after a few GC cycles, rather than RAII-style deterministic destruction at the precise time where an object becomes unreachable.

Here is how I reach this conclusion:

  • If you're writing code in a pure functional style, then you're passing around immutable objects a lot.
  • If you're passing around immutable objects a lot, then to be memory and CPU efficient you must do it by reference, rather than by value.
  • If you're passing objects by reference, then in order to tell when they should be destroyed, you need a way to tell when every reference to an object is unreachable from the program stack, i.e. some kind of garbage collection.
  • Ergonomic garbage collection (without unpleasant manual programmer interventions like designated owners, weak references or manual root tracking) requires a language-integrated tracing GC, which scans references starting from the program stack in order to automatically discriminate reachable references from unreachable reference cycles.
  • Tracing GCs are not compatible with deterministic destruction at the precise time where the last reference to an object is gone, because it would require running a GC cycle every time a reference to an object is destroyed, which is unacceptably costly when references are created and destroyed a lot.
  • Therefore, you want finalizers instead, where objects will only be destroyed once the occasional GC cycle has determined that they are unreachable.
3 Likes

Thanks for the detailed response. There's two ways I see to interpret your argument. (1) Here's intuition on why current languages 'more functional' than Rust don't have RAII and (2) Here's why Rust can't be more functional.

I agree with you on (1), but would not agree on (2).

In particular, I'm not convinced of:

  • Ergonomic garbage collection (without unpleasant manual programmer interventions like designated owners, weak references or manual root tracking) requires a language-integrated tracing GC, which scans references starting from the program stack in order to automatically discriminate reachable references from unreachable reference cycles.

As discussed in previous posts, Rust can be made "more functional" via better pattern matching, TCO, and currying. These have interesting interactions with regards to Drop & ownership, but also have RFCs.

To clarify, I should add:

I agree that there currently probably does not exist a RAII language "more functional" than Rust.

However, in theory, I think such a language could exist -- one can imagine taking Rust, and accepting the RFCs for better pattern matching, tuple assignment (which makes writing TCO code easier).

Oh, I certainly agree that Rust's ergonomics for FP can improve. But I also think that they will probably plateau before it will have reached a level of comfort for functional code comparable with that of an ML or a Lisp.

Programming language design comes with design tradeoffs, and in my opinion, GC is the right tradeoff if you do mainly functional programming, as idiomatic functional code is heavily reliant on shared (immutable) ownership, which Rust's RAII model is not super-nice at handling.

Rust vs Clojure

Before Rust, I spent years doing clj/cljs development, and Rust feels "more functional" than Clojure in the following sense:

  1. I like Rust's pattern matching more than Clojure's destructuring.

  2. Clojure doesn't have TCO either.

  3. I actually am more comfortable building higher order functions in Rust due to type checking. Clojure certainly has nicer syntax for writing functions that take functions as args and returns functions, but I'm scared to use them for anything complicated.

Rust vs ML

I'm not convinced Rust's FP plateaus before reaching ML level. Better pattern matching + tuple assignment + currying + some macros for faking TCO goes a long way.

Rust vs GC

I could be wrong on this -- I believe versions of Mathematica and variants of APL use RC rather than GC -- and both feel very very functional.

I'm not convinced that GC is a fundamental requirement for Functional style programming.

FP often starts with "mathematical definition" then asks "can we make this def recursive .. and executable", and for this, GC certainly makes things easier, but I'm not convinced it requires GC.

In particular, in a 100% pure language, we should be able to do everything with GC, because without mutations, objects form a "DAG" [1] because they can only refer to earlier objects.

[1] There are mutual recursive functions, but in those cases, we can rewrite it as a single function that takes a variant as an arg.