3 ms·
Maybe this is a bad place to ask, but: Those experienced in manual-memory langs: What in particular do you find cumbersome about the borrow system? I've hit som
by the__alchemist 2y ago
Maybe this is a bad place to ask, but: Those experienced in manual-memory langs: What in particular do you find cumbersome about the borrow system? I've hit some annoyances like when splitting up struct fields into params where more than one is mutable, but that's the only friction point that comes to mind.
I ask because I am obvious blind to other cases - that's what I'm curious about! I generally find the &s to be a net help even without mem safety ... They make it easier to reason about structure, and when things mutate.
- rc00 2y ago> What in particular do you find cumbersome about the borrow system? The refusal to accept code that the developer knows is correct, simply because it does not fit how the borrow checker wants to see it implemented. That kind of heavy-handed and opinionated supervision is overhead to productivity. (In recent times, others have taken to saying that Rust is less "fun.") When the purpose of writing code is to solve a problem and not engage in some pedantic or academic exercise, there are much better tools for the job. There are also times when memory safety is not a paramount concern. That makes the overhead of Rust not only unnecessary but also unwelcome.
- the__alchemist 2y agoThank you for the answer! Do you have an example? I'm having a fish-doesn't-know-water problem.
- int_19h 2y agoBasically anything that involves objects mutually referencing each other.
- the__alchemist 2y agoOh, that does sound tough in rust! I'm not even sure how to approach it; good to know it's a useful pattern in other langs.
- int_19h 2y agoWell, one can always write unsafe Rust. Although the more usual pattern here is to ditch pointers and instead have a giant array of objects referring to each other via indices into said array. But this is effectively working around the borrow checker - those indices are semantically unchecked references, and although out-of-bounds checks will prevent memory corruption, it is possible to store index to some object only for that object to be replaced with something else entirely later.
- estebank 2y ago> it is possible to store index to some object only for that object to be replaced with something else entirely later. That's what generational arenas are for, at the cost of having to check for index validity on every access. But that cost is only in comparison to "keep a pointer in a field" with no additional logic, which is bug-prone.
- Cloudef 2y ago>unsafe rust Which is worse than C
- Ygg2 2y ago> The refusal to accept code that the developer knows is correct, How do you know it is correct? Did you prove it with pre-condition, invariants and post-condition? Or did you assume based on prior experience.
- yohannesk 2y agoWriting correct code did not start after the introduction of the rust programming language
- Ygg2 2y agoNope, but claims of knowing to write correct code (especially C code) without borrow checker sure did spike with its introduction. Hence, my question. How do you know you haven't been writing unsafe code for years, when C unsafe guidelines have like 200 entries[1]. [1]https://www.dii.uchile.cl/~daespino/files/Iso_C_1999_definition.pdf https://www.dii.uchile.cl/~daespino/files/Iso_C_1999_definit... (Annex J.2 page 490)
- int_19h 2y agoIt's not difficult to write a provably correct implementation of doubly linked list in C, but it is very painful to do in Rust because the borrow checker really hates this kind of mutually referential objects.
- Ygg2 2y agoHard part of writing actually provable code isn't the code. It's the proof. What are invariants of double linked list that guarantee safety? Writing provable anything is hard because it forces you to think carefully about that. You can no longer reason by going into flow mode, letting fast and incorrect part of the brain take over.
- edflsafoiewq 2y agoOne example is a function call that doesn't compile, but will if you inline the function body. Compilation is prevented only by the insufficient expressiveness of the function signature.
- charlotte-fyi 2y agoIsn't the persistent failure of developers to "know" that their code is correct the entire point? Unless you have mechanical proof, in the aggregate and working on any project of non-trivial size "knowing" is really just "assuming." This isn't academic or pedantic, it's a basic epistemological claim with regard to what writing software actually looks like in practice. You, in fact, do not know, and your insistence that you do is precisely the reason that you are at greater risk of creating memory safety vulnerabilities.
- sgeisenh 2y agoLifetime annotations can be burdensome when trying to avoid extraneous copies and they feel contagious (when you add a lifetime annotation to a frequently used type, it bubbles out to anything that uses that type unless you're willing to use unsafe to extend lifetimes). The solutions to this problem (tracking indices instead of references) lose a lot of benefits that the borrow checker provides. The aliasing rules in Rust are also pretty strict. There are plenty of single-threaded programs where I want to be able to occasionally read a piece of information through an immutable reference, but that information can be modified by a different piece of code. This usually indicates a design issue in your program but sometimes you just want to throw together some code to solve an immediate problem. The extra friction from the borrow checker makes it less attractive to use Rust for these kinds of programs.
- bogdanoff_2 2y ago>There are plenty of single-threaded programs where I want to be able to occasionally read a piece of information through an immutable reference, but that information can be modified by a different piece of code. You could do that using Cell or RefCell. I agree that it makes it more cumbersome.
- Starlevel004 2y agoLifetimes add an impending sense of doom to writing any sort of deeply nested code. You get this deep without writing a lifetime... uh oh, this struct needs a reference, and now you need to add a generic parameter to everything everywhere you've ever written and it feels miserable. Doubly so when you've accidentally omitted a lifetime generic somewhere and it compiles now but then you do some refactoring and it won't work anymore and you need to go back and re-add the generic parameter everywhere.
- the__alchemist 2y agoI guess the dodge on this one is not using refs in structs. This opens you up to index errors though because it presumably means indexing arrays etc. Is this the tradeoff. (I write loads of rusts in a variety of domains, and rarely need a manual lifetime)
- quotemstr 2y agoAnd those index values are just pointers by another name!
- estebank 2y agoIt's not "just pointers", because they can have additional semantics and assurances beyond "give me the bits at this address". The index value can be tied to a specific container (using new types for indexing so tha you can't make the mistake of getting value 1 from container A when it represents an index from container B), can prevent use after free (by embedding data about the value's "generation" in the key), and makes the index resistant to relocation of the values (because of the additional level of indirection of the index to the value's location).
- quotemstr 2y agoYes, but like raw pointers, they lack lifetime guarantees and invite use after free vulnerabilities
- 2y ago
- dzaima 2y agoI imagine a large part is just how one is used to doing stuff. Not being forced to be explicit about mutability and lifetimes allows a bunch of neat stuff that does not translate well to Rust, even if the desired thing in question might not be hard to do in another way. (but that other way might involve more copies / indirections, which users of manually-memory langs would (perhaps rightfully, perhaps pointlessly) desire to avoid if possible, but Rust users might just be comfortable with) This separation is also why it is basically impossible to make apples-to-apples comparisons between languages. Messy things I've hit (from ~5KLoC of Rust; I'm a Rust beginner, I primarily do C) are: cyclical references; a large structure that needs efficient single-threaded mutation while referenced from multiple places (i.e. must use some form of cell) at first, but needs to be sharable multithreaded after all the mutating is done; self-referential structures are roughly impossible to move around (namely, an object holding &-s to objects allocated by a bump allocator, movable around as a pair, but that's not a thing (without libraries that I couldn't figure out at least)); and refactoring mutability/lifetimes is also rather messy.