Skip to content

Comment on Monads, or Programmable Semicolons (2014)parent

Comments

I think lifetimes and lvalues are both reasonably straightforward to understand in terms of linear logic.

IMO, the main difficulty with adding monad-like abstractions to Rust actually lies elsewhere, in the fact that Rust doesn't really have function types -- or rather, it has too many of them. Roughly speaking, Rust does not have a function type constructor. Instead, every function you define in Rust gets its own special, unique type, which implements a function interface -- of which there are three, Fn, FnMut and FnOnce, to represent the different ownership states of the variables captured in a closure.

This means that monad-style interfaces involving higher-order functions and higher kinds will end up needing not just polymorphism over type constructors, but also constrained polymorphism over interfaces. This represents a much more substantial extension to Rust's type system than you might first expect.

The reason Rust does this is because if every definition gets its own type, then type inference can tell you very precisely which functions are called where, which makes inlining a lot more precise and effective. This is an important piece of how Rust turns (for example) iterator-heavy code into efficient loops -- there are actually many fewer indirect calls than it superficially looks like.

Personally, I think this was a design mistake, but it is also a choice that I would not revisit now. Once a system is in the wild, with users who depend on you, we are usually constrained to evolutionary development rather than radical redesigns.

Are you https://www.cl.cam.ac.uk/~nk480/ ??

I wouldn't think Rust's closure types are to blame. I would say Fn, FnMut, and FnOnce are a completely beside the point. In fact, Haskell, scared to worry about the differing structural properties of variables being closed over in its single function type, is about to adopt a substructure extension far weaker (and in my view inferior) to Rust's.

Rust has one (family of) function type(s) `fn(...) -> ...`. Well that and `for<...> fn(...) -> ...`. This is fine. Those traits are just sugar.

Bigger problems are that there is that there is

- No way to abstract over & vs &mut.

- Within a function, we can treat the initialized-ness of individual fields (really lvalues/locations broken down into trees), but across functions everything is initialized or some unsafe escape hatch.

- Lifetimes within a function are arbitrary subgraphs of the CFG, but between functions follow a LIFO discapline to model the stack. We have to break the so we have all 4 combinations. (Also, really the second type of lifetime should be a special stack management thing using with regular lifetimes and not an implicitly different sort of lifetime.)

My problem is not that "Rust just isn't dank enough", but that expressive power is great than its abstractive power: You can do more interesting things than you can hope to reuse. This I think creates some perverse incentives. Haskell (before this linear proposal at least) doesn't let you do anything you can't abstract over, even when it means less fancy tricks than Rust. I like those incentives better.

AboutSource Built by g1lg1l

Hackerly is an independent reader for Hacker News, built on the public HN API. Not affiliated with Y Combinator.