6 ms·
> Tracing GC is the simplest model for the user, and helps with time management and development velocity, two very important aspects of software engineering. >
by Voultapher 3y ago
> Tracing GC is the simplest model for the user, and helps with time management and development velocity, two very important aspects of software engineering.
> Borrow checking is very fast, and helps avoid data races.
One thing many people seem to assume is that not having to care about memory means you can program faster and get to your goal faster. As the author here seems to do. However as it turns out, if your program is more complex than a ~100-1000 lines of code, explaining in a explicit way who owns what and who gets to change state when, is a very useful way to avoid bugs.
Saoirse Shipwreckt aka withoutboats mentioned this a while ago in https://without.boats/hire-me/ https://without.boats/hire-me/
> Rust works because it enables users to write in an imperative programming style, which is the mainstream style of programming that most users are familiar with, while avoiding to an impressive degree the kinds of bugs that imperative programming is notorious for. As I said once, pure functional programming is an ingenious trick to show you can code without mutation, but Rust is an even cleverer trick to show you can just have mutation.
and later follows up on this in https://without.boats/blog/revisiting-a-smaller-rust/ https://without.boats/blog/revisiting-a-smaller-rust/
> I still think this is Rust’s “secret sauce” and it does mean what I said: the language would have to have ownership and borrowing. But what I’ve realized since is that there’s a very important distinction between the cases in which users want these semantics and the cases where they largely get in the way. This distinction is between types which represent resources and types which represent data.
- liuliu 3y agoI don't write Rust. But here is what you said and what the author said don't conflict with each other, and it has been on my mind for a while. People who write similar code, or work on things for decades usually don't really think through what "sketch out some code" looks like. They spend most of their time on refactoring things that has clear use-cases, but not well-defined API boundaries within the component, or between components. So ownerships, nullability checks, data race checks are all comes very naturally as a starter. But there are other side of the world, where people constantly sketching out something, for things like creative arts, high-level game logic, data analysis, machine learning etc. Now putting yourself in that position, the syntax noises are actively in the way of this type of programming. Ownerships, even nullability checks are not helpful if you just want to have partial code running and checking if it draws part of the graph. This is a world Python excels, and people constantly complaining about why this piece of Python code doesn't have type-annotation. We may never be at peace between these two worlds, and this manifest itself somewhat into the "two-language problem". But that to me, is when someone mean "development velocity is faster".
- convolvatron 3y agoI really love parts of rust and kinda hate other parts. but this is what really ruins it for me. I want to play. I want to knock something together and work with it and see what kind of shape it is. rust demands that I cross every last t before I can run it at all. which is great if you already have a crystal notion of what you are building
- snek_case 3y ago> rust demands that I cross every last t before I can run it at all. It's worse than that IMO. Rust makes it very awkward/impractical to have cyclic data structures, which are necessary to write a lot of useful programs. The Rust fans will quickly jump in and tell you that if you need cycles, your program is wrong and you're just not a good enough programmer, but Maybe it's just that the Rust borrow checker is too limited and primitive, and it really just gets in the way sometimes. Some of the restrictions of the Rust borrow checker and type system are arbitrary. They're there because Rust currently can't do better. They're not the gospel, they aren't necessarily inherent property that must always be satisfied for a program to be bug free. The Rust notion of safety is not an absolute. It's a compromise, and a really annoying, tiresome drain on motivation and productivity sometimes.
- yipyip 3y ago[dead]
- jackmott42 3y agoI would never tell you that you are wrong to have cyclic data structures. But there are reasonable workarounds like using handles into an array to do it, which of course re-creates some of the same problems as pointers, but not the worst ones, and is often a positive for performance on modern hardware due to improved data locality. Or you can use reference counted types and take a small performance hit. Or use unsafe and git gud.
- jcranmer 3y agoThe basic model of Rust is to move use-after-free from a dynamic, runtime check to a static, compile-time check. But to keep the static checks from being Turing-complete, you need to prohibit arbitrary cycles while something like a tree (or other boundable recursion) is doable. So Rust not being able to check cyclic data structures isn't a "Rust currently can't do better" situation, it's a "Rust just can't do better" situation. What Rust's intended solution for that is that you add in data structures that do the dynamic checking for you in those cases. But the Rust library doesn't provide anything here that's useful (RefCell is the closest alternative, and that's pretty close to a this-is-never-what-you-want datatype), which means your options are either to use integers, roll your own with unsafe, or try hard to rewrite your code to not use cycles (which is usually a euphemism for use integers anyways). The problem here, I think, is that there is a missing data structure helper that can sit in between integers and references, namely something akin to handles (with a corresponding allocator that allows concurrent creation/deletion of elements).
- verdagon 3y agoI would love a language (or C++ subset!) where we could get the benefits of that secret sauce, while mitigating or avoiding some of its downsides. Like Boats said, the borrow checker works really well with data, but not so well with resources. I'd also opine that it works well with data transformation but struggles with abstraction (both the good and bad kinds), works well with tree-shaped data but struggles with programs where the data has more intra-relationships (like GUIs and more complex games), and works well for imposing/upholding constraints but can struggle with prototyping and iterating. These are a nice tradeoff already, but if we can design some paradigms that can harness the benefits without its particular struggles, that would be pretty stellar. One promising meta-direction is to find ways to compose borrowing with mutable aliasing. Some promising approaches off the top of my head: * Vale-style "region borrowing" [0] layered on top of a more flexible mutably-aliasing model, either involving single-threaded RC (like in Nim) or generational references (like in Vale). * Forty2 [1] or Verona [2] isolation, which let us choose between arenas and GC for isolated subgraphs. Combining that with some annotations could be a real home run. I think Cone [3] was going in this direction for a while. * Val's simplified borrowing (mutable value semantics [4]) combined with some form of mutable aliasing (like in the article!). * Rust does this with its Rc/RefCell, though it doesn't compose with the borrow checker and RAII as well as it could, IMO. [0] https://verdagon.dev/blog/zero-cost-borrowing-regions-part-1-immutable-borrowing https://verdagon.dev/blog/zero-cost-borrowing-regions-part-1... (am author) [1] http://forty2.is/ http://forty2.is/ [2] https://github.com/microsoft/verona https://github.com/microsoft/verona [3] https://cone.jondgoodwin.com/ https://cone.jondgoodwin.com/ [4] https://www.jot.fm/issues/issue_2022_02/article2.pdf https://www.jot.fm/issues/issue_2022_02/article2.pdf
- 634636346 3y ago[flagged]
- cmrdporcupine 3y agoTwo things, full time Rust dev here: a) Rust's borrow checker is good and its type system good, but IMHO it's not really doing what you say it is as well as you're implying: "explaining in an explicit way who owns what"; While ownership is explicit and static (apart from RefCell and friends), description of that ownership is scattered all over, program state flows are not modelled in the type system at all, and on the whole Rust is far from having being a kind of explicit "I can reason about the whole program" declaritive system with the kind of clarity you're implying. Or maybe I'm taking your claims too strongly. b) Rust's borrow checker is good. But it's not perfect and fails to pass things that in fact should be legal borrows. In particular there's edge cases around where things are grabbed in if/let/else or matches, like this fail (from my own code): { let local_version = self.seek_local(tx); if local_version.is_some() { return match &local_version.unwrap().value { Entry::Value(v) => Some(v), // reference to value Entry::Tombstone => None, }; } } // note that 'local' has gone out of scope here and so self should not be borrowed ... code later in func complains 'self' is still borrowed, but the same thing done this way (but less efficiently) passes: if self.seek_local(tx).is_some() { let local_version = self.seek_local(tx).unwrap(); return match &local_version.value { Entry::Value(v) => Some(v), Entry::Tombstone => None, }; } ... same other code that uses 'self' compiles fine In neither case is the 'local_version' being used outside of the lexical scope, and 'self' cannot be borrowed in either case, but the borrow checker is convinced in version #1 that they are and that code below that lexical scope cannot proceed because 'self' is borrowed. They're logically basically equivalent from a program flow and state mgmt, but the second passes while the first fails. Rust 1.7.0 stable. (Before you ask, I did have if/let to take apart local_version instead of using unwrap, and the compiler griped about that even more) Having the burden of how to fix that fall on the programmer sucks. This is all a step in the right direction, but I run into this kind of thing here and there and I shouldn't have to.
- ziml77 3y agoThe limitations of the borrow checker when it comes to borrowing self are annoying. I've had cases where I just said "screw it" and copied the body of a function inline in the 1 or 2 places it was being called just to make the borrow checker happy.