80 comments

[ 3.6 ms ] story [ 134 ms ] thread
I read the entire article, and the actual criticisms seems to be that not everything that can be represented with ownership semantics. I don't see how that would be a "fatal flaw" in any way, and it just seems to be a sensationalized headline.
Especially when they are explicitly taken into account through escape hatches in the ownership system
Yet the safest escape hatches are decidedly limited. Take Rc<RefCell<_>> nodes in a graph (struct Node { edges: Vec<Rc<RefCell<Node>>> }), for example: mutable cyclic traversal is somewhere between difficult (requiring some typically-significant convolution of your algorithm) and impossible. Meanwhile, a GC language can model the same thing trivially. So you start reaching for more dangerous escape hatches, or modelling the problem in some completely different way that lets you skip the problems of ownership semantics for the given domain.
Modeling an arbitrary graph with reference counting immediately leads to the problem of cycles and the need of some form of GC to collect them. It is better to use a GC from the start (not collecting things until the graph owner decides to discard the whole graph is often a useful approach), or reduce the graph to a tree and use weak references for references between branches. Or even use handles as advocated by the article, which essentially reduces the graph to an array and uses its indexes as weak references.
> or modelling the problem in some completely different way that lets you skip the problems of ownership semantics for the given domain

Maybe that's an interesting way to think about the article too. The obvious way to think about a graph using ownership is to start with a root node, then to say that nodes own other nodes through their edges. But that model won't generalise to disjoint graphs (eg, social network with separate friend groups) and it isn't always clear which node is the "root" node.

Graphs are usually persisted as a list of nodes and a list of vertices. The ML approaches usually use tuples or triplets of nodes to represent connectivity and model neighborhood. I wonder if that can not be used to model a graph in a linear system?
Yes, that makes a lot of sense. The ownership would be linear:

    struct Graph {
        nodes: Vec<Node>,
        edges: Vec<Edge>,
    }
but the associations between nodes would have an arbitrary structure.

The article suggests that handles are better than references anyway (I'd tend to agree in this case) so Edge would be a pair of handles. That seems totally fine, so maybe the "fatal flaw" is overstated...

Fundamentally, this most obvious way of representing a graph (each node has a list of adjacent nodes, which are references to the actual nodes) can only work in an ownership-oriented language for directed acyclic graphs. Blockchains, Git commits, directory trees not supporting links, that sort of thing. (Mind you, for practical reasons many systems will still prefer to invert the tree and probably even traverse from leaf to root; this most obvious way is not actually a good fit for computers a lot of the time, even if it’s conceptually beautiful.) But if your graph is not directed, then ownership is not clear (A → B means A owns B, but with A ↔ B you can’t have both A owning B and B owning A), and if it can be cyclic, single ownership definitely falls apart.
Correction: tree, not directed acyclic graph. Multiple paths to a single node is verboten for single ownership. Note that DAGs may still be useful in single-ownership languages: recursive traversal will never touch a visited node, so if you exclusively borrow the whole graph, then you can safely exclusively borrow any node you traverse (which you couldn’t do if the graph was cyclic; in Rust terms, you’d have to check it at runtime).
(comment deleted)
> Ownership semantics are a form of an affine substructural type system1112 which means that they are fundamentally described by a linear logic, and explains why it struggles to express non-linear problems

What is a “non linear logic” or a “non linear problem “ in this context?

I think they're talking about arbitrary graphs and operations on those graphs.
Linked lists and graphs are the canonical examples of things that are difficult to model and express effectively in Rust. In other languages you can just store a pointer/handle/reference to the things each node is connected to, which is commonly not the most efficient way of working (and as systems scale up they routinely replace such techniques with fancier techniques), but is very straightforward conceptually and matches people’s intuitions about how to design such a thing. In Rust, you simply can’t do that directly, roughly because it’s not provably safe (in fact, in the general case, it’s provably not safe). So you have to solve things in other ways, the simplest being to wrap each node in Rc<RefCell<_>> or similar, which is basically a way of saying “I can’t cope with ownership semantics, gimme a kludgy way of checking it at runtime”; but it’s also common to immediately reach for fancier data structures like https://lib.rs/crates/petgraph. Yet even with that, you’re often spitting in the face of ownership semantics with your traversals, because traversing a potentially-cyclic graph is fundamentally against ownership semantics, so you have to be careful because some things will be being checked at runtime.

In the case of these fancier data structures, it is perhaps also worth pointing out that many of them actually do follow ownership semantics, down to things like allowing shared access to multiple nodes, or exclusive access to a single node, so that no runtime checks are necessary. This is where I think the original article severely missteps in calling it a fatal flaw—because people are successfully building safe ownership-oriented abstractions using unsafe code. That’s a super-important capability of Rust.

Even in Java I have problems with non-linear stuff. Clojure is my favorite when it comes to graphs and algorithms that require highly dynamic data structures. I'm not experienced in Rust (but read most of the open-source programming book), but isn't it possible to use macros to create a DSL that can describe those structures? Is this what petgraph does?
I have noticed loads of people are commenting on the "fatal flaw" bit of the title. It is a click-bait title, and if it was anything else, no one would be commenting on the post in the first place. "A Critique of Ownership Semantics" is less interesting to click on than "The Fatal Flaw of Ownership Semantics".
Phil Wadler has several "nice" papers about linear logics, e.g. this one: http://homepages.inf.ed.ac.uk/wadler/papers/lineartaste/line... By nice I mean, nice if you are willing to deal papers about formal logic. Which excludes pretty much most people on this planet. But you asked.

To put it in my own words, and bear in mind that the last time I've worked with linear logic was 15 years ago. A linear logic restricts how often you are allowed to use a proposition in a derviation or proof tree.

The borrow checker is more like a linear type system. In linear type systems you restrict how often an identifier of a linear typed variable may be used. See, e.g. Phil Wadler's paper "Linear types can change the world", where he explores the idea to use linear types as an alternative to model the World(tm) in pure functional programming, i.e. an alternative to the IO monad.

This article compares the hierarchical nature of the type system of OOP languages (think Java) with the hierarchical nature of ownership in languages with Ownership/Move semantics (Rust and newly C++). The author notes that "composition over inheritance" is a rejection of the strictly tree-shaped OOP type system. In the same way, Rust's `RefCell` is an escape hatch from the strict hierarchy of ownership, maintaining a run-time invariant of one owner rather than a compile-time invariant.

One difference I see between these two escape hatches are switching costs. Rewriting the Java standard library to use composition and not inheritance borders on impossibly difficult. Rewriting the rust standard library to use RefCell instead of &mut everywhere would be arduous and possibly automatable, but not impossible.

"...and newly C++"? C++11 had move semantics. So did C++14 and C++17. C++20 still does. It is a stretch to suggest Rust had them longer, considering Rust 1.0 came out well after C++11, and cribbed them. It is a shame that C++ can't be move-by-default and const-by-default, but that's backward compatibility for you.

Rewriting the Rust std lib to use RefCell everywhere would be possible, but would make it almost entirely useless.

Move semantics even predate C++11. The need for move only types was identified very and such types have existed in various form for a ling time.
C++98 had move semantics using std::auto_ptr.
I probably shouldn't have said newly C++, but my frame of mind was relative to the lifespan of the language.

> Rewriting the Rust std lib to use RefCell everywhere would be possible, but would make it almost entirely useless.

Maybe not useless, but certainly worse than it currently is. The point is that you can switch one function to using RefCell (a breaking change, but) totally possible

I would admit "and lately, C++".
How confused can one person be?

(Also: what distinction is being made between "oriented" and "orientated", and why?)

The author appears to believe that the analogies used to invent convenient names for language constructs are supposed to really be the construct, rather than a shorthand description.

Language features, once they map to actual semantics, become pure mechanisms with their own ironclad logic, with only an analogical relationship to whatever concept motivated and, usually, named them.

Thinking in terms of the concept, or (worse) the word chosen to refer to it, as the author does again and again, can only generate confusion.

The author is also confused about the role of inheritance in C++. Unlike in Java, which offers practically nothing else to organize a program, in C++ inheritance is wholly optional, and is typically used only where it solves a specific problem.

The author further confuses regular and virtual member functions, and their respective roles in design space.

The sorts of confusion seen here are quite common in philosophy, which has great difficulty staying grounded to facts of experience, something I think Wittgenstein complained of. In programming most of us don't have that problem, because the facts of problems to be solved and, moreso, of the physical machines we use in solving them provide ground truth.

Computer Science perennially tries to cut itself loose from that ground truth, but is always brought to heel by the demand of its graduates for employability.

> How confused can one person be?

IMO this sentence is completely unnecessary.

> Unlike in Java, which offers practically nothing else to organize a program, in C++ inheritance is wholly optional, and is typically used only where it solves a specific problem.

As someone with extensive experience in both Java and a number of other languages (before and after learning Java) this just isn't true.

It detracts from your point for everyone who knows Java and it adds unnecessary anxiety for people who will learn Java in the future if they belive this.

Java has flaws, but inheritance is not the only way to compose Java programs.

Edit: As a general rule I'd suggest everyone stop pretending Java is generally bad until they have some actual experience from real world systems in Java and a couple of other languages under their belt.

Been a while since I used java but is it still the case that your main function has to be inside a class which has to inherit, ultimately, from Object? I think that's what gp meant in implying that inheritance is not optional.

In practical terms of course a bit of boilerplate to declare a class where none was needed, doesn't matter a great deal.

> but is it still the case that your main function has to be inside a class which has to inherit, ultimately, from Object? I think that's what gp meant in implying that inheritance is not optional.

This is correct but not what GP wrote. What GP wrote is:

> Unlike in Java, which offers practically nothing else [than inheritance] to organize a program,

To point out why GP is wrong:

- In Java you can organize files by packages

- by purpose (different folders for test vs production code),

- you can use composition to combine classes (in fact this is the recommended way these days)

- etc

I normally don't care about language wars but I think it is OK to point out when someone writes something that is clearly wrong.

Also, you can further organize your packages into Java modules (Java >=9).

I don't know why people say that you can't use composition in Java or that it is unique to purely functional languages. You can achieve composition by using Streams, decorator, composite and chain of responsability patterns.

The author also managed to use the same word, linear, in two different senses while pretending those are the same (or very similar) senses.

Bonus points for having a somewhat long list of definitions that explains that the words are used in their usual meaning, yet does this by the way of introducing more undefined terms, sometimes tautological: an Agent is an actor with the capacity to act, marvellous. "A Doer is a doing entity that can do things"--agent and actor are both derived from the same Latin verb that means "to do". And that list misses the definition for "linear", which is definitely not used in any usual sense of the word: trees are linear, but graphs are not... huh?

Yeah by the end of it I was dying for an explanation of what all the uses of linear and non-linear were meant to imply. One paragraph has the words mentioned six times in three sentences!

The whole thing would be much more obvious if they ditched half the philosophising and gave a concrete example of the problem and their solution. Then by all means talk about the philosophy behind how it generalises.

> "linear", which is definitely not used in any usual sense of the word: trees are linear, but graphs are not... huh?

While I agree with your overall point, this is a usual definition of linear: each item (eg graph/tree node) is used (eg pointed to) at most[0] once (eg each tree node has at most one parent node, whereas a graph node can have multiple edges from other nodes).

  0→2  0→2
  ↓ ↓  ↓ ↓
  1 3  1→3
Left is a tree, and linear (each node has only one arrow pointing at it), while Right is nonlinear because there are two ways to get to 3 (via 1 or 2). In this context it also means that if (eg) you print out the graph in a naive S-expression-style traversal you'd encounter 3 twice (eg "(0 (1 (3)) (2 (3)))" when each node is a list).

This sense comes from linear functions, which can be expressed with at most one use of their argument in the form λx(Ax+B) for A and B constant in x. (Under this scheme, x² would have to be expressed as x·x, which repeats x; linearity in programming languages generally allows numeric values to be duplicated freely, so this is only of etymological relevance.)

0: or possibly exactly once; I can never remember which of those is "linear" and which is "affine".

You are partially correct that I have a minor tautology i.e. an actor is something that acts. I wanted to use the term "agent" because "actor" already has a slightly different connotation in common programming speak, and I wanted something a little more specific. That's why I used that term. I do know the etymology of the terms Agent and Actor, but the point I was making is that these abstracted concepts don't actually "do" anything in reality. They are abstractions we use to help us structure and think about programs (regardless of it they are actually useful in practice or not)

I was also using the term "linear" with respect to graphs: * All trees are graphs, but not all graphs are trees. All linear graphs are a form of tree.

Whereabouts was I using the term _linear_ in two different senses?

What is a linear graph? What is "linear hierarchy"? That's the first time I've heard those terms, and I did have a decent Discrete Maths course back in the uni (it did have graph theory and algorithms on graphs). "Value", on the other hand, is indeed generally understood to mean "a datum with an associated type", maybe without "with associated type", pretty much universally since the sixties.

As for two different senses, look at the the first paragraph in the "Mathematical Formalism of Ownership Semantics": it uses "linear graph" and "linear logic" in the same sentence. Unless you define "linear graphs" as "graphs that are described by linear logic", but to the best of my knowledge, linear logic describes the structure of computation (that's why it's logic, it describes the proofs and you get code via Curry-Howard isomorphism), not of data. Unless you equate the graphs with their traverses? But again, that's all highly non-trivial and non-widespread knowledge and terminology, unlike "value is a piece of data" and "agent is an actor".

A "linear graph" is effectively a graph with no cycles in it, i.e. a basic tree.

All hierarchies can be described using graphs, and hierarchy is a relationship between different things in that graph through some dominating aspect (whatever that might be). A "linear hierarchy" is a hierarchy with no overlapping parts, no cycles, and a unique way to traverse up the hierarchy, e.g. you father is your son is not allowed, a node cannot be part of multiple hierarchies, a node has two parents which share a common ancestor, etc.

I am not sure why you brought up my "Value" definition, that was a little random.

> Unless you equate the graphs with their traverses?

I mostly am because data structures on their own without algorithms are useless things. Type systems try to encode information about the values effectively restricting what algorithms can do to them. Ownership semantics are another aspect of the type system of some languages which encodes a responsibility dependency graph to values. This means it explicitly restricts what data structures can be like _by default_. It encourages linear data structures, where the dependency between the values can be model through linear logic. This is why something as basic as a linked list is difficult to implement in languages designed around ownership semantics. It is of course possible but either requires restricting what the linked lists can do, or doing something to bypass the ownership semantics entirely.

> I am not sure why you brought up my "Value" definition, that was a little random.

Because your definition of "Value" is uncontroversial and customary, unlike your definition of "linear graph"--I have met it in your article for the first time. Granted, my knowledge is limited but that's my point exactly: if you bother to introduce the most basic concepts that your readers are most likely already accustomed with, then also please introduce the advanced concepts that you're about to discuss. If you feel that it makes the article too long, maybe consider removing the trivial definitions of the basic concepts, but not the other way around.

I don't want to come off sounding aggressive or rude, and in fact I've read some other articles on your site with great interest, but this particular one leaves a bit of a taste of a maths article written by a crackpot: it starts with talking about what is a number, what is addition, talks about how numbers can also be multiplied and divided, a-a-a-and then in a blink of an eye it goes into very remoted areas of the number theory which may not actually exist (at least Googling them doesn't show anything relevant).

In fact, googling "linear hierarchy" suggests that it's a term from biology, and it doesn't describe a graph or a tree at all: "In a linear hierarchy, there is one individual who dominates all the other group members, a second who dominates all but the top individual, and so on, down to the last individual who dominates no one" (Ivan D. Chase, Kristine Seitz, "Aggression", in Advances in Genetics, 2011). So "linear hierarchy" is apparently a linear (or "total", as it's also called) order, and it's actually consistent with the customary definition of "linear data structure": the one that arranges it's elements in a sequence, so arrays and linked lists (in fact, those used to be called "linear lists", see Wirth's "Algorithms + Data Structures = Programs" from the seventies) are linear data structures, but trees and graphs are not.

To sum it: your usage of the term "linear" seems to be novel, and so it's confusing for someone who hasn't met it already, and arguably most of the readers haven't, maybe define it, even at the expense of defining what a "value" or an "agent" is? Those words you use in their customary senses.

> this particular one leaves a bit of a taste of a maths article written by a crackpot

That gave me a good laugh! I do understand the issue and I am not a brilliant writer in this regard. I was trying to make it understandable to the average programmer but also be at least "correct", and I think I have kind of failed at both in this article. I wanted to make sure that the ideas I was trying to express made sense not just to me or in speech form, but in written form too. I added all those definitions, including "value", because I know so many people who use the terms "value" and "object" interchangeably and assume all values are "objects" in the OOP sense. So I wanted to make a distinction between the two, as I wanted to emphasize the object-ness as the key relationship, not the value-ness.

I am quite interested in how people build mental models of things and how they think about programming in general, but there is very little on the topic currently. And ownership semantics (even the name alone) has a lot of implicit assumptions tagged along to it that I wanted to understand. In sum, "ownership" is the wrong word but oh well, silly history.

And yes, "linear hierarchy" does come from biology. It does mean "linear order" in that sense. That is a much better way of explaining it succinctly.

Interesting you brought up Wirth, since he is one of my idols and that book is absolutely amazing. I always recommend Wirth to beginners wanting to learn how to write compilers.

I noticed the same thing with linear and duly noted it as the four term fallacy
>(Also: what distinction is being made between "oriented" and "orientated", and why?)

My guess is that he's just explicitly acknowledging the Commonwealth English preference for using the verb "to orientate" to mean "to focus/to position", while on the other hand the verb "to orient" is traditionally understood as literally meaning to orientate something in an easterly (=oriental) direction.

I'm a British English speaker, I use the verb "to orientate", but "object-orientated programming" still sounds unnatural to me. It's a bit like "program" and "programme"–we use "program" for software, "programme" for everything else.

I have never heard of "orient" to mean "point something in an easterly direction." The difference between "orient" and "orientate" to my ear is "orient" is a verb and "orientation" is a noun. That means "object-oriented" is a gerunded adjective and "object-orientated" is an compounded adjectival noun. Since the usage is as a gerunded adjective, "object-orientated" is just grammatically awkward. But I'm not a grammatical prescriptivist, so you guys do you.
You are correct. I am British and write in British English. I naturally say orientate rather than orient. It's also a minor joke to add both but whatever.
> The sorts of confusion seen here are quite common in philosophy, which has great difficulty staying grounded to facts of experience, something I think Wittgenstein complained of.

I have to comment this, being myself a multi-class character (both computer science and philosophy graduate). The confusion seen here is quite common when somebody without a proper philosophy background is trying to hold a discourse on abstract philosophical topics. Obviously it happens often in philosophy, but often due to the outsiders (computer scientists, physicists, sociologists, artists) who believe, they have revolutionary ideas ;) Wittgenstein's (or rather Kripkenstein's, depending on the interpretation) were rather of a different, more profound, nature.

In my opinion author has decent ideas but lacks skills in communication to express them properly.

> because the facts of problems to be solved and, moreso, of the physical machines we use in solving them provide ground truth.

It is a very optimistic view of the programming as a knowledge domain. IMHO in real life "The facts of problems" are often very blurry and prone to similar problems seen in the philosophy. I am not talking about academic toy problems, but real-life problems that have to be solved.

Thank you for your comment. I mostly wrote this article as a way to get an idea out of my head rather than present it as a thesis on the topic. I am not a philosopher, but a physicist by profession, so I apologize for my lack of correct terminology to explain the topic correctly.

I do enjoy reading philosophy but there is very little in terms of "philosophy of programming" in order to have a common language to talk about. I could either go full mathematical and most people will not know what I am talking about, or go full example based, but then lose the essence of the problem.

What I wanted to say is that ownership semantics can only deal with "linear" problems whilst most problems are "non-linear" in nature. And that designing an entire language around this idea to ensure "safety", especially without it being "proven" in a smaller language, is/was very risky and dodgy.

I also wanted to explain the mentality that many (not all) people have with regards to ownership semantics and how it regards to a certain concept of "agency", and how it is related (not the same) as the OOP mentality.

P.S. The original commenter is confused about my views, and made mistakes about what I stated. https://news.ycombinator.com/item?id=24999902

It's very difficult, but important thing, to express abstract things in a correct, yet still understandable way, so "kudos" for trying :)

You make a good point, however:

1) > And that designing an entire language around this idea to ensure "safety", especially without it being "proven" in a smaller language, is/was very risky and dodgy

It's not exactly true that ownership models weren't "proven" in different languages. Objective-C comes from the top of my head; the main memory management technique there was reference counting (with the memory pool/manual management fallback). And yes, the non-linearity of some structures was a pain in the ass, one had to be careful to not create a "strong reference loop", but... in the end it worked quite well :) The difference is that in Obj-C one had to do it manually/in runtime, Rust checks it automatically/during compilation.

2) quoting your article "Ownership semantics [...] cannot be used to express non-linear problems".

This is a very strong claim. What does it mean for the problem to be non-linear? I would say, that linearity (in programming) may be ascribed to a model of the problem, not the problem itself, i.e. Traveling Salesman Problem is a problem involving a non-linear structure and still the best known solvers (e.g. Concorde) use linear models to solve it.

It is true that ownership semantics has to be bypassed in the non-linear model, but the question stays whether the given problem really requires one.

Sure, anything Wittgenstein ever thought could never be expressed in anything so crude as human words. Yet, here we are, muddling along.

I have to agree with the author that ownership is a tail that should not be allowed to wag the language dog.

Whereabouts do I confuse the role of inheritance in C++? I just used it as an example of a language which has inheritance built into it. It's completely optional, of course, and I was not suggesting anywhere that it was required. In fact, I rarely use it at all in my own C++ code, and when I do, I usually go the old fashioned C-like approach (for numerous reasons which I won't explain in this comment).

In the article, I define inheritance as the following:

> Most people generally view inheritance as a combination of subtyping and dynamic dispatch through virtual method tables (vtables).

So I did not confuse anything between regular and virtual member functions.

You have made a lot of assumptions about my views which are not even in the text.

And I agree Computer Science is very much not a science in the natural sense. I do with many more people would either say it is just mathematics (formal science) or start giving empirical evidence for their claims that "X is the best way to Y" (natural science).

I suspect that a lot of the inconvenience of modelling non-linear problems in Rust actually doesn’t come down to ownership semantics alone, but the combination of ownership semantics and the low-level memory model. However, I don’t have any solid evidence to support this, because ownership semantics have only really been implemented with the low-level memory model, because the low-level memory model strongly benefits from it (memory safety; it more closely matches how computers work internally at that low level) and GC languages don’t need ownership semantics, and people don’t generally restrict things without cause (and ownership semantics are definitely a restriction).

My thesis here is that Rc<RefCell<_>> and similar are an effective escape hatch to ownership semantics, but they’re much less convenient than a garbage-collected type in a scripting language specifically because of the language’s low-level memory model. A GC language with ownership semantics might be able to obviate RefCell, which is the cause of Rc<RefCell<_>>’s inconvenience.

I’d be interested to see experimentation with ownership semantics in languages with higher-level memory models (which broadly means GC languages). My thesis could turn out to be quite wrong, and I haven’t worked out all the details of how the combination would work (especially I’m hazy on parts of removing RefCell, which is pretty critical), but I think it could be worth some research—ownership semantics for its own sake, rather than ownership semantics for memory safety.

> 10. Getting used bypassing the borrow checker to reduce fighting implies people have just found a way to cope with the constraints it imposes.

Here’s the thing, though: it’s not just about finding a way to cope with the constraints it imposes; it’s also about thriving within the constraints it imposes, because it stops you from doing problematic things and guides you in the path of better designs much of the time (though non-linearity can definitely be a problem; it’s not all buttercups and daisies). When I work in JavaScript, I spend half the time missing ownership semantics, occasionally feeling downright miserable because ownership semantics would have made a problem far easier or less dangerous. Remember what I said about ownership semantics for its own sake rather than ownership semantics for memory safety? Yeah, most of the time I love Rust’s ownership semantics for the modelling rather than the memory safety.

Yes, I think you're onto something. Essentially what if we modeled linearity like in Rust, but then did GC over the maximally linear data graph we can find. I've been wanting to play with some ideas in this direction, but haven't had the time.
At the machine level there is no ownership. A computer has fixed amount of RAM and CPU can change an arbitrary word at any time.

Ownership is an attempt to partition that with the goal of simplifying things and say that particular area of memory can only be modified/accessed at particular time by particular code. Isolated memory between processes does that at OS level, Rust and OOP try it inside a particular process, but they do it in a different way.

I wondered if anyone would call me out on that, when I first wrote it! I agree: at the most fundamental level data types aren’t real; even pointers don’t exist, they’re just a convention. What I was trying to get at is that at the fundamental level everything’s a value; when comparing high-level and low-level memory models, the key difference is whether everything’s a value or a reference (though many high-level models still support some kind of value types). When you have value types, ownership is real, and reflects how the machine works. Because the real world of CPUs isn’t quite that theoretical fundamental level: there are instructions for dealing with pointers; the concept of the stack is built in; things like that. If you have a stack variable, it’s inherently owned by the one place (though whether the language freely permits mutation from multiple places is another matter). So yeah, in the end I maintain my position that ownership semantics more closely match how computers work at that low level.
I agree! At the most fundamental level (that I know of) all bits are two ranges of voltage, light, or magnetism in a system (although I am aware of deeper understandings based in physics). We have decided to give the meanings of 0 and 1 to those ranges. We have also given meaning to collections of bits by calling them pointers or ints. We have given meaning to other collections of bits by encoding various symbols in them and a logic based on those symbols.

My point here isn't to be pedantic but to argue this point: The fundamental quality of computing is that it abstracts a physical system to a logical one. In this light the realness of a data type is the same realness of a bit, since they are both structures that give way to a more defined logical system, one built on the next.

To speak to the OP's claim of a category mistake I think he misses this valuable insight. The "realness" of programming objects and the semantics of moving them and copying them is the same "realness" of the bit or the data type. This is to say that the category that programming objects are contained within is, for lack of a better phrase, "the category of logical abstractions built on physical systems". The reuse of "move" and "copy" to represent similar processes to those we use with physical systems is a "logical encoding", not an error. In fact, the op makes the categorical mistake by saying that we mean "physically move" when we mean "logically move".

That said, this was an interesting read in spite of my disagreements with some assertions.

> I’d be interested to see experimentation with ownership semantics in languages with higher-level memory models (which broadly means GC languages).

Haskell might give a few hints how well this works. [1]

> Here’s the thing, though: it’s not just about finding a way to cope with the constraints it imposes; it’s also about thriving within the constraints it imposes, because it stops you from doing problematic things and guides you in the path of better designs much of the time (though non-linearity can definitely be a problem; it’s not all buttercups and daisies).

Oh yes, and how nice it is to have these constraints verified by the compiler. Perhaps it's this kind of assumption, that less constraints give you more power, without realizing that also the responsibility increases, and at a certain point the complexity overburdens the human mind.

Different software domains need different constraints, so the question might be where the sweet spot between languages with different constraints and a language with programmable constraints (aka dependent types) lies.

[1] https://www.tweag.io/blog/2020-06-19-linear-types-merged/

> it’s not just about finding a way to cope with the constraints it imposes; it’s also about thriving within the constraints it imposes

This is an extremely important point! Most people try to make a language act like another language, rather than try and embrace how it does things. "When in Rome(, do as the Romans do)"

It's like when people try and make C into another language by abusing the macro system, you get a horrible mess compared to if you embraced what the language likes to do normally.

Personally, I think Rust's memory model is quite high level, and coupled with its ownership semantics, I cannot do many things such as custom allocators. (Not a singular custom global allocator)

Lots of interesting thoughts, and a good outline of ownership's limitations, but I think some big leaps were made. "Fatal flaw" is quite a hyperbole, and the comparison to OOP, while interesting, is much too hand-wavy to be used as evidence for that assertion.

But they were spot-on about what is arguably Rust's biggest weakness, and the things people do to work around it, and I think there's kind of an open question whether that status-quo can be improved upon. I.e. Rust right now is excellent at certain things ("linear things"), and okay at non-linear things. But could it be made to be really good for the latter too? Or will it remain somewhat confined to its specialization? The language occupies uncharted territory either way, so these kinds of questions are not surprising, and I for one am excited to see where things go.

> I.e. Rust right now is excellent at certain things ("linear things"), and okay at non-linear things. But could it be made to be really good for the latter too?

Having written programs in Rust and Haskell, which are in this regard quite similar, I think that you can express most of your application in a "linear" fashion and only have a bit of "non-linear" things at the top.

For example if you have to handle some "non-linear" things like events from the system or a gui library, then you might have - in Rust terminology - a 'RefCell<Rc<AppState>>' to be able to have shared mutable access to the application state.

At the top of the event handler you would transform the 'RefCell<Rc<AppState>>' into a '&mut AppState', which is the switch from the "non-linear world" to the "linear world" and involves a runtime check that there's only one mutable reference. All the following code of the handler can then be verified at compile time.

This also makes the reasoning about the application easier, because you might even reduce the mutation of your application state to the switching area. So most of the event handling could be a pure transformation of the application state.

I think Rust (or a new Rust-like language) could potentially be improved significantly by having language-level sugar for things like Rc<RefCell<T> Arc<Mutex<T>> or even some kind of Gc<T>.
If you find yourself wishing you didn't have to type some combination of types so often make a type alias. But the primary reason there's no built-in shortcut is that you might want to compose them in a different order.
> "Fatal flaw" is quite a hyperbole,

I think one should just assume headlines are hyperbole and live with it. No one would read the article "A nuanced critique of ownership semantics" no matter how good it was. The only reason we are discussing it is because someone chose to make it hyperbolic. That's unfortunate but it is what it is.

I would read it.

The article is crap overall given what the title promised.

I’d do it too - but we wouldn’t see it because it wouldn’t be here on the front page, so we’d be much less likely to even stumble across it.
An advantage of reserving language like "A Fatal Flaw in Foo" for situations where there really is a fatal flaw in Foo is it gives headlines more dynamic range.
The problem with that is that you can't actually have that advantage because of headlines like this one engaging in loudness wars.
I am the author of the article and the "Fatal Flaw" is hyperbolic on purpose, and a mild reference to the downfall of a protagonist in a story.

As for where other languages can go in this area, it would be interested to explore the idea of tracking "uses" in general. It might be useful for the compiler but not necessarily an intrinsic language feature. My issue with Rust is that it went headlong into an unproven idea and those issues are now showing.

I’ve reached the bit about Aristotelian metaphysics (why??) and I still can’t tell what is the author’s point.

EDIT: Alright, I’ve skimmed to the end and it seems to me it is a very r/iamverysmart way of saying “I prefer to use RefCell”.

Honestly, I have trouble trusting someone’s judgement in programming language semantics when they can’t seem to be a good judge of their own written language semantics.

Why define an agent at the beginning, but leave “agency” undefined? What does even “capability to act in a certain environment” mean? Is a function/method something with “capability to act in a certain environment”? Is a C++ class that??

Also:

> It is also a category error to treat them as “real objects” since “real objects” and “programming objects” have little connection with each other ontologically.

Mate, you link to the Wikipedia article about categorical errors, where it says that a CE is defined as comparing things that are ontologically different, thus showing your whole sentence as a tautology. What’s the value added for the reader??

Minor nitpick: "Taking a value by self Drops the original value" is wrong, moving a value does not call Drop on it.
This minor observation caught my eye, very well articulated:

"There are many criticisms of OOP456789 but my general criticism is that by placing emphasis on trying to solve problem in the type system, it shifts focus from the data structures and algorithms, the core of what a program fundamentally is."

More confusion: anything solved in the type system drops off the list of what needs to be solved with data structures or algorithms, for a big gain. Essentially, math has there replaced programming.

We are not interested in solving all problems by programming. We should solve with programming what is best solved that way.

This is not a confusion but a fundamental difference in viewpoint. Not everything can be, nor should it be, solved at compile time.

"anything solved in the type system drops off the list of what needs to be solved" AT A HUGE COST. Everything has a cost to it and even if you could hypothetically most solve things at compile time (you cannot), it might take a damn long time to compile it.

True, a difference in viewpoint: your HUGE COST is to me PRACTICALLY FREE.

Some things can be solved before compile time, some during compile time, some during startup, and some only by running as long as needed. The earlier the better.

If you like, you can think of compile-time as its own runtime.

For me, performance is much more critical, and the languages with ownership semantics do not easily allow me to get those performance benefits unless I ignore/bypass the ownership semantics of the language. It's one of the reasons I started my own language, the Odin programming language.

I wanted the ability to have control over memory layout and memory allocations, and be tied to a specific model of how structure and design my program determined by the programming language itself, especially ideas which have yet to prove themselves as being "good" (by whatever metric).

"Odin"? The truth comes out.

I confess I am also not very impressed by the idea of organizing a whole language around ownership. But that doesn't make move semantics a problem.

It is a problem that Rust doesn't let you code your own move. It is another problem that C++ allows it to fail, or to decay to an ordinary copy.

> Dealing with singular values can be very useful, but not everything is a value. Some things are fundamentally “non-values” e.g. instructions/control-flow/declarations.

If there are important things in your language that aren't values, that's a problem in your language. You really don't need any non-values; just look at, say, Haskell.

> As I have described above, both the OOP and OS both share similarities:

> Traditional-OOP: A (linear) value hierarchy of behaviour. The values act as agents.

> Ownership Semantics: A (linear) value hierarchy of responsibility. Agents are responsible for values.

This is just bollocks once you actually look at the details of it. An inheritance hierarchy is fixed once and for all at compile time, and can't ever change; either there's no way to change which class a given class inherits from at all, or at best there's some uncontrolled dark magic you can do. Whereas a defining characteristic of ownership semantics is that ownership can be safely passed from one owner to another in a controlled way.

> Object orientated programming is a form of misinterpreted and misapplied Aristotelian Metaphysics applied to a domain it was never meant to model

Ha! I always felt like OOP is a natural outgrowth of Scholasticism, which is basically saying the same thing.

In my simple peasant brain I always associate "ownership" with a simple chunk of memory, not with "objects" as in OOP.

As soon as a piece of code "knows" about this chunk of memory it has certain responsibilities, like not randomly scribbling over values or freeing the memory while others pieces of code might attempt the same.

"Ownership" and "Move Semantics" are entirely artificial conventions to give some structure to those responsibilties. Casting those conventions into "hard language rules" introduces tradeoffs, evaluating those tradeoffs is important when deciding what language to use to solve certain problems. And this is where Rust comes in. Rust just enforces a couple of such "made up" conventions, none of those rules are dictated by the "real world" (e.g. the hardware the code runs on)... it's easy to get all tangled up in theoretical concepts like type systems, and functional programming, but in the end the only thing that counts is the machine code that's running on a concrete CPU. Sometimes I feel that language designers are losing sight on this simple reality ;)

I absolute agree with you, but you already knew this. I think the terms "ownership" and "move" are confusing and what it really is "responsibility".

> ...ownership/move semantics is fundamentally based around the _responsibilities of values_ by tracking value usage. > ... > If we were to call Ownership Semantics a paradigm, it would be the orientation around the responsibility of values in a hierarchical fashion, placing emphasis on this system of responsibility, shifting focus from data structures and algorithms.

I am not a brilliant writer (which I already knew, even though loads of people have commented on that), but I was trying to get an idea I had in my head onto paper. I wanted to explain that this abstraction is not as good as it is being advertized and does have its limitations. It's not a criticism of the concept, but its application to a bunch of problems.

I failed to find an example of a practical problem in the article. What I did find was a load of circling around the subject from a distant philosophical perspective, all boiling down to essentially "you know what, there exist problems that ownership semantics aren't suitable for" (but using lots of wisely-sounding language), forgetting to present the problems.

The article also suffers from the syndrome of taking a feature of a language and pretending that everything has to be seen through the lens of that feature. Well, newsflash: ownership semantics apply to only some things in Rust. They apply to values and bindings. But they don't apply to e.g. indices. I can take as create as many i's, where i=5, all referring to the same Vec, which later are going to be used to mutate the Vec, as I like, and Rust won't stop me. Similarly, there is Rc and Arc.

It also included one of those "wise" thoughts along the lines of "You know what? The true nature of computers/economy/life/love is..." - one of the templates of meaningless sentences. In this case it was "however virtually all computers are fundamentally mutable things". Hmm... Did we suddenly forget about pipelining, instruction ordering, caching, instruction-level parallelism, C/C++'s problems with memory aliasing? C is not a low-level language[1].

[1]: https://queue.acm.org/detail.cfm?id=3212479

If we were to write a statement analogous to the one advocated for in the article, but talking about mathematics instead, it could be "the fatal flaw of mathematics is that a lot of it is about reducing problems to linear algebra". How ridiculous does that sound?

The title isn't "The Fatal Flaw of Programming". I'm not sure why you think mathematics is to linear algebra what ownership semantics are to... Rust I guess?
If anything, that would be inverted. But anyway, just as linear algebra is just one tool in a mathematician's arsenal, ownership semantics are just one tool in a Rust programmer's arsenal.
One of the points in the article is that most problems are non-linear in nature, rather than linear. But a language like Rust, has designed its entire type system around this concept of ownership semantics which can only deal with linear logical problems. The biggest appeal of Rust to many is that it is "safe" and it is "safe" because of its ownership semantics and lifetime semantics. There are many other reasons to use Rust, of course, but this is the main reason it was made. I would argue that its "safety" is fundamentally the biggest reason to use it.

I don't disagree with you that C is not a low level language, but a very high level language. But you have made a false dichotomy regarding "mutable things" and then talking about other things about a computer. I am not saying "a computer is only X", but "a computer is X, but also Y, Z, W, ..."

The "fatal flaw" bit is purposeful click bait. "A critique of ownership semantics" is not necessarily a good.