12 ms·
This is a great post demonstrating just how bad the ergonomics of Rust are (I know I know, that's not the point of Rust). But for people who care about language
by iandanforth 4y ago
This is a great post demonstrating just how bad the ergonomics of Rust are (I know I know, that's not the point of Rust). But for people who care about language usability you can point to this post and say "they were not thinking about the user journey during the design process, how would you have designed it differently?"
Take the first two sections as a perfect example. You decide you want to introduce a single-character control symbol `&` (which is questionable by itself), how do you apply it? In one case you decide that this control symbol indicates that a value is immutable and in another it's part of the declaration of a value as explicitly mutable. You've now introduced a semantic contradiction that a learner has to overcome.
Now obviously Rust works, and that's its point, and people love that enough to overcome all the ergonomic challenges the language puts up. What I can't wait for is Pythonic Rust where we have a language that treats humans as first class citiziens but offers the guarantees that Rust does. The only question in my mind for if this will ever happen is if the fully literate programming modality enabled by LLMs will make it a moot point.
- oleganza 4y agoStrongly disagree. I am the person who hates C/C++ and likes languages like Ruby. I like Rust specifically for the reason that I don't have to learn C++ like "grown ups" and can write my crypto libraries in something much more ergonomic. When I started writing in Rust, it was 2018 and there was old borrow checker (NLL was experimental) and error messages were much more annoying than today. And still, that was nice language that is easy to write and read once you understand a couple of basic rules. Here are the rules that you need to learn and always keep in mind, even if Rust had a more verbose syntax. And I bet many people struggling with Rust simply were not lucky enough to have those rules explained to them. Rule 1. By default, in Rust things are "owned" and "moved", all the time. When you do `x = y`, you literally move "y" to "x". In other popular languages you might expect automatic copying or automatic referencing, but in Rust you get an unusual "move" semantics. This rule applies universally to all things: be it simple numbers or structs, or references, or weird Rc<T> things or whatever. Once you move "x" to another location, you cannot use it any longer from the old location. Rule 2. As an optimization, some simple types are marked "Copy" which means that you get a cheap implicit copy whenever you want to continue to use moved value. This applies mostly to numbers and fixed-sized types. This liberates you from explicitly calling ".clone()" on stupid integers. Rule 3. There are references, mutable and immutable, that let you avoid moving value when you don't need to. At any point of time, each value may have N immutable references or 1 mutable one. There are lexical references (most commonly used): & and &mut, for these the rule 3 is statically checked at compile-time. And there are runtime counterparts that implement rule 3 in runtime: Cell/RefCell. So when you design an API or use an API, you should really think about ownership of the values that you pass in according to these 3 rules. You may have these questions and use the rules above to answer them. - Should that API "consume" (get the value "moved into") that value, or should it have read-only or mutable access to it? - What part of code (which other value) actually owns the value and is responsible for cleaning it up? - Is that a dumb value that can be Copy-ied explicitly, or is there non-trivial copying cost that requires explicit references or clones for it? - What does it mean to say `y = &x`? Per rule 1 we move reference "&x" to "y", but since it's a non-mutable reference, we can have a lot of them, so it implements Copy and &x can be used again per rule 2.
- justincredible 4y ago[dead]
- PaulHoule 4y agoThe essential complexity of borrowing in Rust is more fundamental than the accidental complexity of the syntax that’s required to express borrowing. You could make something more verbose and a little more self-documenting and people will still struggle to write programs for which borrowing isn’t a good fit.
- eternalban 4y agoCame to say the same thing. Rust requires explicit annotation of usage semantics. This information somehow has to get conveyed to the compiler and runtime. So the 'ergonomic' issues are fundmantal. I think from a PLT perspective, the interesting question is 'can the problems Rust is supposed to solve be resolved in a simpler (but radically different) conceptual model'? We started off originally with a spatial conceptual model -- stack, heap, scope -- that mapped lifecycle semantics to spaces and transitions between spaces. Concurrent programming stressed out that model and we're here with explicit mapping of semantics by the programmer. I've been wondering lately whether a new spatial approach can yet again simplify matters.
- rom-antics 4y ago> I've been wondering lately whether a new spatial approach can yet again simplify matters. I've been closely following the development of Vale since I first saw it here. Though their approach is slightly higher-level than Rust and requires (some) runtime safety checks (though to be fair, so does GC). https://vale.dev/ https://vale.dev/ https://verdagon.dev/blog/zero-cost-memory-safety-regions-overview https://verdagon.dev/blog/zero-cost-memory-safety-regions-ov... I think it would be tough to change the spatial model in a language as low-level as Rust, because that spatial model is just reflecting how your CPU actually works under the hood. If you try to hide that away, the programmer is going to end up losing some control.
- deleted 4y ago[deleted]
- PaulHoule 4y agoFor most applications, garbage collection. For a while I was thinking of the question of "Why didn't production rules (a.k.a. expert systems, business rules engines) catch on as a general approach to programming?" Systems like that can do some remarkable things, similar to Excel and logic programming in that you can write programs that aren't concerned with the exact sequence of events that things have to be done in. This is not only convenient for the non-professional programmer, it makes some "chicken-and-egg" problems in conventional programming languages trivial, back in the 1980s people would have seen this to answer to parallel programming, today we might see it as an answer to the async choreography hell of front end programming. The thing is that sometimes the order of events matters and when you look at rules engines they never settled on a standardized way to control execution. There are numerous things that work, an advanced rules engine implements several, but none of them are 100% satisfactory for everyone. One answer to these problems I see is "rules and schemes" where there are two programs that exist side-by-side, one that defines what is to be done and the other that fills in the details of how exactly it is done. I can see something like that for the general programming language case. (Write a "style sheet" that explains how memory management is done) Also sometimes I fantasize that the way to achieve what Rust is trying to achieve is some combination of (1) A really good macro assembler (2) A theorem prover (3) An ergonomic parser generator that makes writing rich DSLs easy and completely mainstream. (1) is a good project (e.g. gas is by and for people who hate assembly language), (3) is a good project (I can rant about how bad parser generators hold developers back), the (1)-(2) punch is appealing with the the caveat that portability and optimization are problems in that case, but those could be addressed by developing architecture-specialized macro packs and (4) A general-purpose superoptimizer which hooks into (2) which is itself a project the world needs.
- avgcorrection 4y ago> This is a great post demonstrating just how bad the ergonomics of Rust are (I know I know, that's not the point of Rust). A chainsaw is meant to saw wood. But it should still have a good usability. Err, sorry. I meant to say hammer. > But for people who care about language usability you can point to this post and say "they were not thinking about the user journey during the design process, how would you have designed it differently?" People have been discussing how to make Rust usable on the rust-lang issue trackers and RFC repositories all the time that I’ve observed from the peanut gallery. Before the stable 1.0 release as well. It’s not for a lack of trying.
- jamincan 4y agoThat said, I'm looking forward to seeing what the first generation of languages influenced by Rust will look like. A first iteration on an idea is always going to make mistakes that a second iteration can do away with.
- verdagon 4y agoI'm particularly excited for this too. We're starting to see a lot on the horizon: * Austral is using linear types for zero-cost memory safety [0] and then allows for borrowing on top of that. Val does something similar called mutable value semantics, which is pretty interesting [3]. * Vale brings borrow checking up to the regions level [1] which gives borrow checking benefits while still allowing mutable aliasing, which makes it much more flexible and ergonomic. * Cone uses borrow checking on top of any user-specified allocation strategy, such as single ownership, RC, GC, etc. [2] [0] https://borretti.me/article/introducing-austral https://borretti.me/article/introducing-austral [1] https://verdagon.dev/blog/zero-cost-memory-safety-regions-part-1-immutable-borrowing https://verdagon.dev/blog/zero-cost-memory-safety-regions-pa... [2] https://cone.jondgoodwin.com/ https://cone.jondgoodwin.com/ [3] https://github.com/val-lang/val https://github.com/val-lang/val
- avgcorrection 4y agoNote for others: This person (based on nick and domain name) is the “lead for the Vale Programming Language” but didn’t feel like disclosing that fact in this comment.
- zer0zzz 4y agoPythonic Rust you’re speaking of sounds an awful lot like Swift.
- Ygg2 4y agoSwift uses ARC everywhere from what I recall. Sure it's ergonomic. But why not just go for full GC then?
- pjmlp 4y agoRC is a GC algorithm, so it is already there.
- oleganza 4y agoNo it is not. The whole point of GC is to collect cycles and not think about ownership, while Swift and Rust require you to think about ownership. In Rust you do so via borrow checker or explicit Rc/Weak wrappers. In Swift you do that via additional weak annotations (strong being the implicit default).
- Ygg2 4y agoGrandparent is correct. Reference counting is a garbage collection mechanism. In same way a bubble sort is a sort. It doesn't care about ownership either. Apart from some Swift specific optimizations iiuc. Full GC I mentioned is more of mark and sweep variety.
- furyofantares 4y agoAlright, malloc/free is also garbage collection then.
- Ygg2 4y agohttps://en.wikipedia.org/wiki/Garbage_collection_(computer_science) https://en.wikipedia.org/wiki/Garbage_collection_(computer_s... See Reference counting. Note lack of malloc/free.
- jstx1 4y agoWhen people tell me that Rust is nice to write it feels like gaslighting. I want a smooth path from not having code to having running code and Rust doesn't offer this at all.
- unshavedyak 4y agoI think it depends on what alternate paths the person is thinking of. I throw most GC languages out. So now my path is mostly (not all) Rust, C, C++, etc. Rust is great in that regard, for me at least. The stdlib is awesome, crates are awesome, powerful features like Iterators are awesome, and being confident in the code i produce is awesome. Which isn't a knock at C, but C just isn't for me.
- wyager 4y agoI'm not sure how to interpret the "smooth path" thing in a way that applies to most languages but not rust.
- xvedejas 4y agoWhen you find the path you need smoothest is the path from compiling/running code to _correctly working_ code, I think that's where Rust shines. Yes, writing code that will compiler is harder; and yes, you _will_ learn to more effortlessly write code that compiles.
- Ygg2 4y agoThat's subjective. I find it mostly natural (until you get into type bound fuckery) but I can't for love of God get good at Prolog or Lisp.
- JohnFen 4y agoGood point. I'm finding Rust unpleasant (but expect that once I've crested the learning curve, this will change), but I find Lisp very intuitive and pleasant. Probably has something to do with how we conceptualize things.
- emschwartz 4y agoDepending on what language you're coming from, I think that's totally fair. A part of Rust that I very much appreciate is that once you write Rust code and get it to compile, there's a pretty good chance that it's going to do what you expect. As someone who is new to Rust, it's definitely no small feat to get the code to compile in the first place. But I think that's one of the main things people tend to love once they get over the initial hump of grokking all the different ways of passing variables around.
- pcstl 4y agoI have a lot of criticisms of Rust's usability, but I think you're being pretty much just unreasonable here. One of the first things a Rust novice will see when picking up the language is: let a = 1; // a is immutable let mut a = 1; // a is mutable Then a bit further in they will learn about references, and that there are two kinds of references: a: &i8 // a is a immutable reference a: &mut i8 // a is a mutable reference I believe it is pretty clear that "mut" is what signals mutability, while "&" signals that something is a reference. There is next to no reason to associate "&" intrinsically with mutability, since you will have seen "mut" in a non-reference context and you will have seen "&" in a non-mutable context.
- iandanforth 4y agoThe OP is entirely about how `&` is misunderstood and misused, and how just adding `&` to appease the compiler is a common learner behavior. The author then explains `&var` and `&mut var` not in terms of their nature as references but in their aspects of mutability. Your view is the way understanding of the language should proceed, but I argue that it is not the reality in fact.
- pcstl 4y agoFair enough. I understood your phrasing more as a criticism of the language's design, and that is where I disagree, but I agree that the article could be structured better.
- ordu 4y ago> how just adding `&` to appease the compiler is a common learner behavior. If learner is unfamiliar with the very idea of references, then his/her problem is not an ampersand, but how to grasp the distinction between reference and value. Rust makes it hard, because it takes its own view on how variable relates with its value: Rust variable holding a value behaves much more like a reference to the value than a variable in C does. It is hard to notice with Copy+Clone types, like integers, but it bites in the ass with others. Variable in C is associated with its memory. When you write `int a;` you create a variable which has `sizeof(int)` bytes of memory. It doesn't work with Rust, a place in memory is an attribute of value not of variable. `let a: i32` doesn't allocate memory, memory will come with a value, when you assign the value to `a`. I mean it is hard to grasp at first. I came to Rust from a C world, and struggled to get this simple fact for a few months. It seems stupid in retrospect, it was written all over the place in Rust Book, but my mind took the relationship between a variable, memory and a value as a kind of an implicit dogma, and failed to notice how it breaks in Rust. But I digress. So variable in Rust has some properties of a reference. And then there are "real" references like &, &mut, RefCell, Rc/Arc. These didn't made my life harder, they work like my C-mind expected them to work, but I assume that people who come from a higher level languages can struggle to grasp all the differences.
- cornholio 4y ago> What I can't wait for is Pythonic Rust where we have a language that treats humans as first class citiziens but offers the guarantees that Rust does. The innate complexity of the ownership rules make a Pythonic Rust unlikely, someone must manage the memory so you either put up with that complexity, manually do it with malloc/free, or let the garbage collector do it (in which case it's not Rust anymore). Perhaps you could have a Go-like Rust where everything that escapes the function or breaks ownership is automatically transformed in a reference counted object (which is still a form of garbage collection, but deterministic and compatible with system programming). So that the syntax is simple and programs are easy to compile, but you might get performance bugs when the program does heavy use of the ref count, invalidating cache lines left and right etc.
- 15155 4y ago> or let the garbage collector do it (in which case it's not Rust anymore). Arc<T> is still Rust. Not ideal, but very much still Rust. I wouldn't be surprised if despite wrapping absolutely every object in Arc, Rust still were to perform better than most high-level languages at whatever given task.
- tialaramex 4y agoArc isn't a garbage collector though, Arc is just reference counting. You could write this by hand. Languages which have a GC may in some cases also do reference counting to reduce the load on the GC, but they need the GC because reference counting doesn't collect cycles. It's trivial to make a cycle, then destroy all references to it - and in Rust that cycle lives forever, it can't be destroyed because each part of it has references from the rest. With a GC this gets collected, eventually, exactly when will vary which is why in the GC language destructors aren't as useful for resource clean-up as they are in languages like C++ or Rust.
- verdagon 4y agoI think the future is in languages that offer "opt-in" borrow checking, so you can use it where it makes sense. After all, most of a program should prefer simplicity over performance, and only optimize where it matters. I think this is the path towards a more Pythonic Rust. Some languages that are going this direction, including D [0] and Vale [1]. Vale's particular insight is that we can get the benefits of borrow checking with isolates (from Pony) and integrating "pure blocks" and pure functions into the type system, so to speak. > ... breaks ownership is automatically transformed in a reference counted object ... Some languages actually do this! Lobster [2] uses borrow-checker-like semantics and falls back on reference counting. And worth mentioning is HVM [3], which falls back to cloning. [0] https://dlang.org/blog/2019/07/15/ownership-and-borrowing-in-d/ https://dlang.org/blog/2019/07/15/ownership-and-borrowing-in... [1] https://verdagon.dev/blog/zero-cost-memory-safety-regions-part-1-immutable-borrowing https://verdagon.dev/blog/zero-cost-memory-safety-regions-pa... [2] https://www.strlen.com/lobster/ https://www.strlen.com/lobster/ [3] https://github.com/HigherOrderCO/HVM https://github.com/HigherOrderCO/HVM
- rom-antics 4y agoOn the other hand though, it brings symmetry to types and values, and makes destructuring feel natural. let i: i32 = 1; let &i: &i32 = &1; let &mut i: &mut i32 = &mut 1; let (i, &mut j): (i32, &mut i32) = (1, &mut 2); I'm not sure how you'd recreate this symmetry without using the same symbol in both places.
- sakex 4y ago`&T` does not mean it's immutable. It means it's borrowed. `&mut T` means it's borrowed mutably `T` and `mut T` have the same logic but for owned variables. I don't get the issue there.
- JohnFen 4y agoWait... as a Rust newbie, you bring up a point I could use clarification on. I get that "&mut T" means it's borrowed mutably, but doesn't that automatically mean that "&T" means it's borrowed immutably?
- burntsushi 4y agoTo a first approximation, yes. To a second approximation, "&T" is a shared borrow and "&mut T" is an exclusive borrow. The key point to bring up in the rephrasing here is that "&T" doesn't imply you can't mutate something. Why? Because interior mutability (for example, RefCell and Cell, but always underpinned by UnsafeCell) permits mutation through "&T". RefCell, for example, can be thought of as "borrow checker, but at runtime." In the days of yore, an event called the "mutpocalypse" occurred where by (pre Rust 1.0) there was a huge discussion about potentially changing "&mut T" to "&only T" or "&uniq T" because the more precise model was one of sharing vs exclusivity, rather than immutability vs mutability. The immutability vs mutability model is still useful, it's just wrong in more circumstances. However, it does have the benefit of (IMO) being more accessible by describing borrows in terms of the most common effect: to mutate or not. I don't know which choice would have been the right one, but I am at peace with it. "Rust Atomics and Locks" by Mara Bos has a great discussion of these points toward the beginning of her book, and specifically grounds the choice of which terminology to use in concrete and practical terms, which I really appreciated.
- JohnFen 4y agoHmm, OK. I think I follow this. So, "&mut T" is a bit of a misnomer, then? I can roll with remembering that.
- 4y ago
- pjmlp 4y agoSee Val for a possible step into that direction. https://www.val-lang.dev/ https://www.val-lang.dev/ Or how the Chapel language for HPC is going at it, https://chapel-lang.org/ https://chapel-lang.org/
- smolder 4y agoI think your idea of what Pythonic Rust might be goes against one of Rusts goals: not having hidden, costly abstractions, and instead giving the developer explicit control. If you don't feel you need control over when data is copied, when there should be a mutex, atomic reference counting, and so on, you're probably reaching for the wrong language. IMO giving us explicit control of low level behavior is not a detriment to ergonomics as much as it is a benefit. Add in the package manager and great compiler and it feels like first class dev experience to me.
- deleted 4y ago[deleted]
- convolvatron 4y agoI'm a low level programmer, and programming in rust feels exactly like someone is taking the control I need to do my job away from me. maybe I'm wrong, and I can get it back by reverse engineering the whole stack and getting underneath things like tokio.
- JohnFen 4y agoAs a Rust newbie, here's a chance for my understanding to be corrected, but... Isn't taking control away from the programmer the entire point of Rust? I thought the premise of it is that programmers can't be trusted. That sounds like a criticism, but I don't mean it as such. It's just what I hear when I hear people talking about the value of Rust. Is my understanding incorrect?
- convolvatron 4y agono. I don't think you're wrong. I was just adding a counterpoint to the above. as an old lisp programmer I really think systems should be less opaque. make things accessible and simple and useful, but make it easy to keep unwrapping. rust disturbs that journey by #deriving over the underlying truth to make things simple. but when you have to crack that open its a lot stuff to digest. I don't think rust is _wrong_, I just think that value statement you just made should be more explicit. and maybe rust devotees shouldn't be so quick to tell you that your high level goal is incorrect because its something rust doesn't handle well.
- marcosdumay 4y agoThey thought quite a lot about the user journey. And it's very clear that the user they thought about is an experienced C++ developer. I bet the Rust developers never even dreamed of their language being easier to learn than C++. But it happened to be, so a lot of people go directly to it, and get the problems you are talking about. Unexpected success always causes some problems.
- Animats 4y ago> And it's very clear that the user they thought about is an experienced C++ developer. Yes. If you had to work in C or C++, Rust is a huge improvement. If you only know Javascript or Python, Rust seems way too finicky. (If you had to work in assembler, C was a huge improvement.) Rust ownership is language support for something C and C++ programmers had to think about anyway. The real problem with Rust ownership is that backlinks are poorly supported. If you need a tree with links pointing back to the root in Rust, it's very difficult. There's Rc and Weak, but now you have to use .upgrade() on a weak link before you can use it, and it can fail. You can have all the objects owned by a Vec or HashMap, and refer to them by handle, but that's essentially manual pointer manipulation. Single-ownership with backlinks checked at compile time would be a big help for some data structures.
- JohnFen 4y agoI've been learning Rust because it's clearly going to be an important language moving forward. I get what it's trying to do, but I do have to admit that I'm finding it an unpleasant and ugly language to use. But it just joins the group of other unpleasant and ugly languages. It is what it is, and in a sense, it's following a tradition that's been around since the beginning.
- Tanjreeve 4y ago>Now obviously Rust works, and that's its point, and people love that enough to overcome all the ergonomic challenges the language puts up. What I can't wait for is Pythonic Rust where we have a language that treats humans as first class citiziens but offers the guarantees that Rust does. Those guarantees in anything more than a hello world application are hard to achieve. When people say "i want extremely high performance and safety but I want it to be simple and guessing what Im doing" are asking for completely contradictory things and normally what you get out is something that leaves you up the creek the moment you are not following their ideal use case. >The only question in my mind for if this will ever happen is if the fully literate programming modality enabled by LLMs will make it a moot point. Much like the above. It'll work fine for hello world applications. Anything beyond that it will be much harder because the complexity comes in making an application coherent both internally and with the external systems it interacts with. Which is fine. Plenty of stuff nowadays can be abstracted.
- singularity2001 4y agoSwift had the potential to be Pythonic Rust but has a horrible server / package story.
- zer0zzz 4y agoTotally spot on. Swift could be this incredible language with all sorts of dialects suited for lower or higher level programming. But the ecosystem and corporate sponsors surrounding it continue to ensure that it remains nothing more than a UI App language for one kind of Mobile phone. It's pretty darn unfortunate IMHO. The state of SourceKit and SPM are pretty sad IMHO.
- arc619 4y agoAgree with all of this. The Nim language might fit the bill of treating humans as first class citizens, and certainly fits better with my need for productivity and performance without over-specifying details. The language focuses on readability first and borrow checking is an automatic compiler optimisation behind the scenes, with full type bound, constrained borrowing/move semantics being opt in as required. There's loads of sensible ergonomics like this, for example large immutable parameters are automatically passed as pointers for you while the compiler enforces the immutability in the code, making things simple, efficient, and safe by default, with less cognative overhead or need for these kinds of micro-optimisations leaking into the architecture. Saying that, Rust‘s multithreading story is better for now as it currently offers wider aliasing protection although arguably at the cost of developer friction.
- jackmott42 4y agoSuggest an alternative syntax that would be less confusing? The & symbol is familiar to many people because it has similar meaning in C,C++, Go, and other languages. Rust gets the unique mix of safety and performance by making you have to deal with memory, that is fundamentally more complicated.
- zainhoda 4y agoSpot on. I think Rust could have been even better if it didn’t have all this ugly syntax. I would like to see something more akin to Swift.