10 ms·
Stop making me memorize the borrow checker
- capitol_ 2y agoThis sounds like a reasonable complaint, that refactorings are too complex in large programs. But I have a hard time to envision any other solution than "better tooling for refactoring".
- pornel 2y agoTrue, you need to know what is and isn't possible in the borrow checking model to avoid learning it the hard way after writing the code. There are some gotchas that you need to learn (e.g. self-referential structs won't work, or & returned from a &mut method won't be shareable). But besides a few exceptions, it's mostly shared XOR mutable data in the shape of a tree. It's possible to build intuition around it.
- mjevans 2y agoThis makes me further appreciate how golang's features tend to work entirely at compile time, which is also fast. One of the other things that makes me worry about Rust is how similar it's depends look to npm projects, where there's a kitchen sink of third party (not the language's included library of code, and not the project's code) libraries pulled in for seemingly small utilities.
- danpalmer 2y agoGo’s features work only at compile time, but are far more limited. I experience more crashes in Go than in any other compiled language because of how limited it is as a language.
- kstrauser 2y agoRust’s checks are also evaluated and enforced at compile time.
- hypeatei 2y agoDependencies are optional, and having a huge standard library also has its tradeoffs. If the standard library has a less than ideal API, it's stuck with that until a major version bump and you either: 1. End up with a third party package filling in the gaps, or 2. Another standard library API that users slowly migrate to
- saghm 2y agoIt's also a lot easier to release a new version of a package to fix a bug than do a bugfix release for the entire language toolchain, which is what would be needed in order to update the standard library. With Rust releasing a new minor version every six weeks, I think minimizing the chances of additional releases needed in between them is probably a good thing.
- tsimionescu 2y agoUnfortunately, in a world with increasingly more sophisticated attackers looking at supply chain attacks, having a lot of dependencies, especially ones that update regularly, is a huge security risk. For a language like Rust, which aims to be both low level and used in secure environments, I would argue that the risks far outweigh the benefits. We'll see how this works, Rust is still young and not yet used in any hugely important projects (or at least not in hugely important parts of those projects - e.g. some Linux drivers, not the core kernel; some bits of Firefox'S rendering, not the JS engine). As it becomes more central, it's value as an attack target will increase, and people will start taking infiltrating malicious code in small but widely used dependencies.
- deleted 2y ago[deleted]
- hypeatei 2y agoThe same could be said for all of the utilities used on Linux (that they're increasingly becoming huge targets) as seen by the recent XZ backdoor[0]. The open source model of limited funding and maintainer burnout are an inherent risk to any project. Rust is not special here. 0: https://en.wikipedia.org/wiki/XZ_Utils_backdoor https://en.wikipedia.org/wiki/XZ_Utils_backdoor
- maxbond 2y agoI think it's the natural state of affairs for a "folk standard library" to emerge. I don't think pydantic or serde should be part of their standard libraries. But I will use them in most projects. In ten years, the "folk stdlib" will probably be a different set of packages (perhaps a superset, perhaps not). Don't push the river; if it's natural, manage it rather than fighting it. Trying to anticipate all or even most use cases in the standard library is a fool's errand (unless we're talking about a DSL, of course). There are too many and they are too dynamic to be captured in the necessarily conservative release process of a language implementation. Languages should focus on being powerful and flexible enough to be adapted to a wide variety of use cases, and let the community of package maintainers handle the implementation. Think of this as a special case of the Unix philosophy; languages should do one thing very well, not a million things unevenly. I bet most people here don't believe a command economy could ever work in a market for goods and services. Why should it work in a marketplace of ideas?
- riwsky 2y agoAnd new cars tailored for each consumer’s use case will emerge in ten years, too—that doesn’t make it any less awful to live in areas that lack good public transportation.
- maxbond 2y agoI'm not sure I understand the metaphor? Let me know if I'm off base. If the suggestion is that putting things in the standard library makes them better, I disagree. My experience with Python for instance is that a "batteries included" strategy results in some phenomenal packages and some borderline abandoned packages that are actively dangerous to use. To riff on your metaphor, the federal government designs the arterial highways, but the state, country, and city/town officials design the minutia of the traffic system. If the federal government had to approve spending on replacing some street signs or plowing snow, we would have a terribly impoverished transportation system.
- riwsky 2y agoThe suggestion is that, while it is possible to overdo a stdlib, it is also possible to underdo it. For two examples: plenty of languages leave auto-formatting and testing to the community, functionality which rust is better for having standardized.
- rokob 2y agoI agree with this. However memorizing the borrow checker rules has led me to architect code “more correctly” upfront and consider things like ownership that I was otherwise pretty lax about ahead of time before. I think this has made me more thoughtful even in other languages which I think has been a win for me. That said, the tedious refactors are a real pain. I think we all hoped that rustc would be smarter by now. It has gotten better but it isn’t there yet.
- kstrauser 2y agoSame for me. I think I write better code in other languages now.
- foxhill 2y agowhat a miss. i’d consider myself a day-to-day c++ engineer. well, because i am. i like lots of things from rust. there’s a few things i don’t. c++ has a lot to learn from rust, if it is to continue to exist. but really.. isn’t this the point of the language? you need to understand the borrow checker because.. that’s why it’s here? maybe i’m missing something.
- kstrauser 2y agoRust was a pain in the ass until I stopped trying to write C code in it and started writing idiomatic Rust. I don’t know the author of this blog, but he mentions extensive C++ experience which makes me wonder if he’s trying to write C++ in Rust. Maybe not! Maybe it’s truly just Rust being stubborn and difficult. However, it’s such an easy trap to fall into that I’ve gotta think it’s at least possible.
- maguirre 2y agoAre there examples one can learn from about idiomatic rust? I would appreciate either books or projects to learn from.
- kstrauser 2y agohttps://doc.rust-lang.org/book/ https://doc.rust-lang.org/book/ is great. I’d been writing Rust for months before I started reading it and still began learning new things from the start. Oh, that’s why it does this! Edit: Oh! And use “cargo clippy” regularly. It makes excellent recommendations for how to make your code more idiomatic, with links to docs explaining why it’s nicer that way.
- galangalalgol 2y agoRust, like ocaml, is best when used purely functionally until you run into something that isn't performant unless its imperative. But unlike ocaml or haskell there is a safe imperative middle ground before going all the way to unsafe. People who write modern C++ with value semantics etc. seem to have a lot less trouble than people coming from Java.
- lowbloodsugar 2y agoI mean, I don’t write it that way, but if it works for you. I wouldn’t say you have to write it that way so I wouldn’t want to put anyone off. Thinking about your answer a bit more, one of the paradigms of Rust is “there shall be many immutable references or just one mutable reference” and so I can see that functional programming would naturally lead to that. But it’s a paradigm that works with the underlying principles rather than the true nature of the language, IMHO. I do it by thinking about different domains of object graphs, and how data moves between them, for example.
- sestep 2y agoIt is interesting that the borrow checker doesn't run until after typechecking succeeds. As far as I'm aware, rust-analyzer has its own builtin logic for doing typechecking, but it delegates to rustc for borrow checking. I wonder whether this is just a temporary situation due to lack of engineering resources to implement borrow checking in rust-analyzer; personally I doubt that, especially since gccrs is incorporating components of rustc wholesale and so I'd be a bit surprised if rust-analyzer moves in the opposite direction. In theory it seems possible to support borrow checking in the IDE for ill-typed programs, but having borrow checking as a separate analysis pass depending on successful typechecking is just such a nice abstraction boundary to have for maintaining the toolchain.
- dwattttt 2y agoborrow checking relies on types to be able to check; types are what carry borrows after all.
- sestep 2y agoThis is true but misleading: analogously, typechecking depends on parsing, but IDEs typically make a best effort to typecheck syntactically ill-formed programs.
- estebank 2y agorustc does its best to recover, continue and provide diagnostics from later stages. But at the same time it is better to provide a single early error and mark the entire node as recovered to avoid further errors at the cost of requiring more cycles of back and forth, over the alternative of tons of useless knock down errors that drown the underlying cause of the problem. We are always on the lookout for improving in this area. Having examples of cases where we conceivably should have done better but didn't is useful. As mentioned already, the complexity here is that doing the right thing for the user requires architecting multiple separate stages of the compiler to talk to each other in way more complex ways than originally intended.
- amelius 2y agoYes, and people should stop using Rust for projects that do not require a systems programming language. Know what tool to pick.
- kstrauser 2y agoI used it for an API server and it was pleasantly fun. “Systems” is a broad brush.
- eYrKEC2 2y agoIt's a high level language with algebraic data types and fantastic type checking. I'll use it where ever I darn well please.
- runeblaze 2y agoSometimes I write Rust with Arc and Rc sprinkled everywhere because I just need a dialect of OCaml.
- estebank 2y agoThe only thing that's really missing is deref patterns so that you can pattern match on an Arc<T> with a T pattern.
- goku12 2y agoShouldn't this be possible already? Deref is implemented for Arc<T>. [1] https://doc.rust-lang.org/std/sync/struct.Arc.html#impl-Deref-for-Arc%3CT,+A%3E https://doc.rust-lang.org/std/sync/struct.Arc.html#impl-Dere... [2] https://rust-lang.github.io/rfcs/2005-match-ergonomics.html https://rust-lang.github.io/rfcs/2005-match-ergonomics.html
- estebank 2y agoI mean having the following work: match Arc::new("hello") { "hi" => {} "hello" => {} _ => {} } It is an extension of match ergonomics, called deref patterns. There's some experimental code to make it work for `String` to `&str` (`if let "hi" = String::from("hi") {}`), but it is not anywhere close to finished. The final version will likely have something like a `trait DerefPure: Deref {}` signaling trait. There is disagreement on whether giving users the possibility to execute non-idempotent, non-cheap behavior on `Deref` and allow them to `impl DerefPure` for those types would be a big enough foot-gun to restrict this only to std or not.
- James_K 2y agoAn often overlooked solution to this problem is to avoid using Rust, or to only use it for performance critical code. Writing a large application in Rust sounds hellish, it seems like it would be much nicer to only use it in sections of the hotpath where it is absolutely necessary.
- goku12 2y agoThat applies only if you're struggling with Rust. It's as good as any other general purpose programming language once you're out of the fight-the-borrow-checker stage. Structuring or refactoring large applications in Rust is nowhere as tedious as many project it. There are many zero-cost abstractions and other features that even makes it very pleasant. My first preference for making simple utilities is as a shell script. The immediately next one is actually Rust.
- James_K 2y agoManually managing memory complicates the program, and makes it harder to change. Every interface written is contaminated with the implementation details of how it manages memory. This has a large cost in terms of development time. As much as you might want to imagine Rust has made every other programming language obsolete, that just isn't true.
- goku12 2y agoYour assertion doesn't match my experience. Besides if that were true, nobody would be using C or C++ for writing general purpose (non-system) applications. C and C++ require the same system knowledge that Rust developers use to satisfy the borrow checker. Even worse, Rust borrow checker will remind you of those rules. C and C++ will just allow you to proceed and crash. C and C++ memory management is even more manually involved than Rust's. Yet people do write normal applications in C and C++. The only explanation I can think of for the dislike towards Rust's compile time checks is that some people don't entirely understand these rules when they use C and C++. It's possible to resolve simple memory safety issues in C/C++ without in-depth knowledge of hardware semantics. But a complicate bug will easily stump you at runtime (personal experience).
- lowbloodsugar 2y agoMy opinion so far is that people want to write Java using Rust, or they want to write C++ using Rust, and they have a hard time. You can’t really just go write a program architected the way you do in those other languages. It’s not “C++ with a borrow checker”. So I’d say it’s “worse” than “you have to memorize the borrow checker”. Its “you have to learn how to write programs in Rust”.
- melvyn2 2y agoThe borrow checker exists to force you to learn, rather than to let you skip learning. To make an analogy, I think it would be weird if I complained that I had to "memorize the rules" of the type checker rather than learning how to use types as intended.
- Pannoniae 2y agoFair enough, but the problem in this analogy is that this learning isn't always useful or productive in any way. This is more like doing arithmetic in a sort of maths notation where every result must be in base 12 and everything else must be in base 16. Sure, you can memorise the rules and the conversions but you aren't doing much useful with your life at that point. Obviously, the borrow checker has uses in preventing a certain class of bugs, but it also destroys development velocity. Sometimes it's a good tradeoff (safety-critical systems, embedded, backends, etc.) and sometimes it's a square peg in a round hole (startups and gamedev where fast iteration is essential)
- deleted 2y ago[deleted]
- kstrauser 2y agoI think it destroys productivity if and only if you don’t roll with it and do things the Rusty way. If you write code with its idioms, it can be a huge productivity boost. Specifically, I can concentrate on fixing logic errors in my code instead of resource bookkeeping. When I refactor something, I know I didn’t accidentally forget to move alloc/free to the appropriate places for the new code: if my changes broke something, it’ll tell me.
- Aurornis 2y agoRust shouldn’t “destroy development velocity” once you’ve grasped the core concepts. There is some overhead to being explicit about how things are shared and kept, but that overhead diminishes with time as you internalize the rules.
- 2y ago
- jchw 2y ago> Rust is not the answer, it is simply a step towards the answer. True that. On one hand, it's amazing. On the other hand, the nagging feeling that we still have work to go in programming language design has not gone away.
- maxbond 2y agoThere is no answer. There are only improvements.
- tialaramex 2y agoTwo examples of things I want, built-in on day one, in some future language: Structured concurrency. Don't provide "legacy" mechanisms and "opt in" for structure, just bite the bullet. Like the first language which told people no, we don't "go-to" other functions, that's not happening in my language, that was structured program flow I want structured concurrency, it's a thing but it's not yet popular enough to do that as the only provided concurrency, it should be. Smart arithmetic. Your computer has Floating Point math. FP math is fast, but, it's hard for humans to think about exactly where they lose precision and performance while using it. I should be able to write the real mathematics I want, specify the precision I need and possibly the performance trades I'm comfortable with, and the compiler not me the programmer, figures out how to use FP math to calculate my mathematics with acceptable precision or tells me that I made demands it couldn't meet or which are nonsensical. On the other hand, two things I really liked to see when I learned Rust: &[T]: The slice reference type, a fat pointer which specifies where zero or more contiguous Ts are, and, how many of them. This is the Right Thing™ and it's right there in the core language design, which means you don't need to go back and retro-fit it. String: The simplest possible way to build the growable text type, as a growable array of bytes but with the strict requirement that the array's content is always UTF-8 text. Is the "Small String Optimization" a cool trick? Yeah, but it need not live in this core vocabulary type. How about Copy-on-write ? Ditto. What about other text encodings? Transcode at the edges if you need that.
- ilaksh 2y agoI sincerely believe that it is nearly impossible to have an objective and constructive conversation about the merits of programming languages, because the language of choice becomes part of people's worldview. So it's like discussing politics or religion. People think that they have objective views, but they can't overcome their beliefs. That's just how beliefs work. They almost never change. Also, beliefs are tied to groups. Humans automatically adopt the beliefs of their group, at least to some degree. Or they learn to shut up about their disagreements. This is a thread for Rust critics and Rust advocates. Try to seriously sell F# or some other ML-like language in here and you are going to end up annoying both the C++ people and the Rust people. The world will be a better place when the AIs finally take over. If we survive.
- Arch-TK 2y agoI have memorised the UB rules for C. Or rather, more accurately, I have memorised the subset of UB rules I need to memorise to be productive in the language and am very strict in sticking to only writing code which I know is well defined (and know my way around the C standard at a level where any obscure code I sometimes need to write can be verified to be well defined without too much hassle). I think Rust may be difficult But, if I forget something, or make a mistake, I'm screwed. Yes there's ubsan, there's tests, but ubsan and tests aren't guaranteed to work when ub is involved. This is why I call C a minefield. On that note, C++ has such an explosion of UB that I don't generally believe anyone who claims to know C++ because it seems to me to be almost infeasible to both learn all the rules, or at least the subset required to be productive, and then to write/modify code without getting lost. With rust, the amount of rules I need to learn to understand rust's borrow checker is about the same or even less. And if I forget the rules, the borrow checker is there to back me up. I still think that unless you need the performance, you should use a higher level language which hides this from you. It's genuinely easier to think about. That being said, writing correct rust which is going to a: work as I intended and b: not have UB is much less mentally taxing, even when I have to reach for unsafe. If you find it more taxing than writing C or C++ it's probably either because you haven't internalised the rules of the borrow checker, or because your C or C++ are riddled with various kinds of serious issues.
- akira2501 2y ago> This is why I call C a minefield. Computing is a series of "minefields." At least you get a map of this particular one. I'm far more confronted by public facing APIs that involve user authentication than I am of any particular documented set of language facts.
- tialaramex 2y agoThe ISO document for C has an appendix which lists all the known categories of Undefined Behaviour. It's not exactly a small list, but it's something you could memorize if you wanted to, like the list of all US interstates, where they start and where they end. There has been a proposal to attempt this for C++ but IMO the progress on making such an appendix is slower than the rate of the change for the language, making it a never ending task. It was also expanded by the fact that on top of Undefined Behaviour C++ also explicitly has IFNDR, programs which it declares to be Ill-formed (ie they are not C++) but No Diagnostic is required (ie your compiler doesn't know that it's not C++). This is much worse than UB.
- skp1995 2y agoRust can be hard to get right because of the borrow checker. I had a similar situation happen to me where I went about refactoring the code to make borrow checker happy ... until the last bit when things stopped compiling and I realized my approach was completely wrong (in the rust world, I had a self-reference in the structs) Having said this, the benefits of borrow checker out weight the shortcomings. I can feel myself writing better code in other languages (I tend to think about the layout and the mutability and lifetimes upfront more now) My rust code now is very functional, which seems to work best with lifetimes. I would love to know more about the authors pain, I do hope rustc gets better at lifetime compilation errors cause some of them can be very very gnarly.
- estebank 2y ago> I do hope rustc gets better at lifetime compilation errors cause some of them can be very very gnarly. When this happens, file tickets! We do our best to improve diagnostics over time, but the best improvements have been reactive, by fixing a case that we never encountered but our users did.
- skp1995 2y agowill keep that in mind going forward! The most recent ones which I have been hitting are around "higher-ranked lifetime error" I know my way around this now, which is to literally binary search over the timeline of my edits (commenting out code and then reintroducing it) to see what causes the compiler to trip over (there might be better ways to debug this, and I am all ears) Most of the times this error is several layers deep in my application so even tho I want to ticket it up, not being able to create a minimal repo for anyone to iterate against feels like a bit of wasted energy on all sides, do let me know if I should change this way of thinking and I can promise myself to start being more proactive.
- estebank 2y agoIf it's public code, a link to a branch with the issue can still be useful. Looking at the compiler internals you can get a better sense on how to minimize the issue. That being said, not having a minimised repro lowers the chance of it getting addressed quickly. Even if you have already figured out how to deal with it, your future colleagues might not, and by improving the diagnostic you would also be getting that time manually commenting code back.
- ufmace 2y agoI don't really agree with this. I'd phrase it more as, you have to learn to really understand what the borrow checker is trying to do and how it makes you architect your programs and consider that ahead of time. Once you understand that, you'll rarely have problems with the borrow checker. It does preclude significant chunks of styles and data structures often used in other languages though.
- Const-me 2y ago> it makes you architect your programs and consider that ahead of time This only works for projects which do not involve any R&D, but have a complete and well written functional specification written in advance. Also for projects which do a complete re-implementation of some pre-existing software. For greenfield projects which require substantial amount of R&D, it’s impossible to architect programs ahead of time. At the start of the development, people only have a wishlist. Architecture comes later, after several prototypes implemented and evaluated, and people have some general understanding what does and doesn’t work, and what specifically needs to be done. Rust implies that upfront architecture costs even for prototypes.
- Olumde 2y agoAgreed. This is probably the reason why rewriting in Rust "works" -- because the architecture has been previously worked out.
- ufmace 2y agoI don't really agree with that interpretation. In my opinion and experience, it does not restrict architectural choices to the extent that it makes it difficult to develop greenfield projects. It's more that it rules out a relatively small subset of architectural choices which are arguably a bad idea anyways, as they do infact have flaws that may not be obvious at first but will lead to a lot of pain if the project grows above a certain size.
- Const-me 2y ago> rules out a relatively small subset of architectural choices which are arguably a bad idea anyways Just because an algorithm is not representable in safe rust doesn’t mean it’s a bad idea. See my other comment in this topic https://news.ycombinator.com/item?id=42164582 https://news.ycombinator.com/item?id=42164582 You’ll find similar parallel algorithms in all high-performance BLAS libraries. On my day job I sometimes do similar things in C++, using OpenMP or other thread pool for parallelism.
- throwaway81523 2y ago> This is stupid, because the whole point of having a type system or a borrow checker is to tell you when you get it wrong, so you don’t have to memorize how the borrow rules work. Sweet summer child. Use Haskell for a while and get back to me.
- Ygg2 2y ago> This means that in order to write C++, you effectively have to memorize the undefined behavior rules, which sucks. > This means, to be a highly productive Rust programmer, you basically have to memorize the borrow checker rules, so you get it right the first time. This is stupid, because the whole point of having a type system or a borrow checker is to tell you when you get it wrong I'm not sure how you want to square this circle, you don't want to memorize the rules of UB, but you also don't want for compiler to correct you when you make UB behavior according to Borrow Checker? The best way in both C++ and Rust is to structure your tree of lifetimes and use other means to achieve your desired goal.
- recursivecaveat 2y agoThey just don't want the borrow checker errors to only come after type checking. You have to know the rules well because otherwise you're going to finish a big refactor only to find out afterwards it was never going to work.
- Ygg2 2y agoThat's one of their issues. Other issues are borrow virality and stuff for next Rust.
- dinobones 2y agoI've written a ton of software, both backend and embedded-like software in C++. What are people writing that requires such fancy/extensive usage of the borrow checker? I can't even remember the last time I had to use a shared_ptr... unique_ptr and other general RAII practices have been more than enough for our codebase.
- loeg 2y agoI think OP is likely overusing references. The vast majority of code doesn't need to deal with explicit lifetimes.
- andybak 2y agoOP sounds fairly smart and my first thought is "if OP is struggling then Rust probably isn't for me". Who is Rust for?
- kstrauser 2y agoIn some ways, people who aren’t heavily invested in other languages to the point that they think in them. Neophytes know less, but they also have fewer things to unlearn.
- loeg 2y agoI don't think it's useful to think there is some single spectrum of intelligence that makes one more or less suited to using Rust. Rust is for anyone who finds it useful.
- Const-me 2y ago> I've written a ton of software, both backend and embedded-like software in C++ Me too. > What are people writing that requires such fancy/extensive usage of the borrow checker? The simplest example I can imagine is this: https://en.wikipedia.org/wiki/Matrix_multiplication https://en.wikipedia.org/wiki/Matrix_multiplication When your matrices are large and you want it to run fast, you want to parallelize. Good algorithms (which don’t bottleneck on memory bandwidth) need multiple CPU cores to concurrently store different elements of the same output matrix. Moreover, the elements computed by different cores are not continuous slices, they are rectangular blocks. Such algorithm is not representable in safe rust.
- troad 2y agoI think it's telling that whenever someone raises concerns about any element of Rust, no matter how constructively, they're always met with a wall of "you must not truly get the borrow checker," or "you're using Rust wrong," or "stop trying to write <C/C++/Java/etc> in Rust!", usually with zero evidence that that is in fact what is happening. There's never anything to improve on Rust, it's always user error / a skill issue. If there ever surfaces any audio of Linus Torvalds and Ken Thompson discussing the pros and cons of the borrow checker, I expect a sea of patronising anime avatars to show up, seeking to explain Rust's invention of the concept of ownership to them. Rust is really nifty, but there are still (many) things that could be improved in Rust, and we'd all benefit from more competition in this space, including Rust! This is not a zero sum game. Honestly, I also think many people just want a nice ML-like with a good packaging story, and just put up with the borrow checker to get friendly C-like syntax for the Option monad, sum types with exhaustive matching, etc. This is a use case that could very much benefit from a competitor with a more conventional memory model.
- NoboruWataya 2y agoNo one thinks there's nothing to improve in Rust, there are lots of features it is missing, some of which are in nightly or on the roadmap. But the borrow checker and the concepts that underpin it are pretty fundamental to Rust and what separates it from other languages. If you like Rust except for the borrow checker, then I would think you don't really like Rust.
- troad 2y ago> If you like Rust except for the borrow checker, then I would think you don't really like Rust. The sentence in my original post that this addresses is supportive of the emergence of an alternative language to Rust for people with this use case, so I think we're just agreeing. (Although I wouldn't go so far as to tell others what they do or do not like based on my own ideas of what is essential and what isn't.)
- __float 2y agoI can't help but feel this is a somewhat veiled complaint about the Rust community instead of anything substantive with the language :/
- FridgeSeal 2y agoCan't say I agree, or that this matches my experience of writing Rust. I don't memorise how it works, I've just learnt what it rejects and why, and this in turn becomes clear as to why it's rejected that. Very rarely do I find myself going "oh bother, now I suddenly need to `Rc` or `Arc` this, I suspect because I've just gotten into the habit of suspecting when I anticipate things will run afoul and structuring things from the get-go to avoid that. Admittedly, I'm not writing absurdly low-level code. I wonder if the authors grounding C++ is making life harder for them? Often when I've had to teach people Rust, getting them to stop writing {C/C#/Java}-but-in-Rust is the first stop on the trail to "stop fighting and actually enjoy the language". Every language has its idioms, just because you can, doesn't mean you should.
- skp1995 2y agoI do have to ask, I have worked in codebases which used lifetimes and didn't lean into Rc/Arc and vice-versa. I used to think Arc/Rc was a shortcut to avoiding the borrow checker shenanigans, but have evolved that thinking over time. You do mention it in your comment so wondering if you have anything to share about it
- s17n 2y agoIf you aren't writing low level code, why not use a GC language?
- FridgeSeal 2y agoGreat type system, great performance, great packages, great tooling, nice high-level API’s that produce low overhead code, I’m most familiar in Rust now. I’ve had more than enough unpleasant experiences with write-runs-breaks-at-runtime style languages. I don’t like writing them, I hate having to support them in prod as they give me constant-persistent-stress. I hate what idiomatic C# is, and how much ceremony there is to read and write it. Java is worse. Go’s type system is too anaemic for my tastes. Haskell is nice, but gets a bit academic and lacks some day-to-day niceties. Kotlin is supposed to be nice, but again, we’re getting maybe 50% of Rust type system features, and you’re basically just piggybacking on Java/JVM which I hated dealing with previously. IDK what else that leaves in the mainstream. I used to play around with Nim, and that was quite nice though.
- Aurornis 2y agoA theme I’m noticing more frequently as Rust gains popularity is people trying to use Rust even though it doesn’t fit their preferred way of coding. There is a learning curve for everyone when they pick up a new language: You have to learn how to structure your code in ways that work with the language, not in the ways you learned from previous languages. Some transitions are easier than others. There should come a point where the new language clicks and you don’t feel like you’re always running into unexpected issues (like the author of this article is). I might have empathized more with this article in my first months with Rust, but it didn’t resonate much with me now. If you’re still relying on writing code and waiting for the borrow checker to identify problems every time, you’re not yet at the point where everything clicks. The tougher conversation is that for some people, some languages may never fully agree with their preferred style of writing code. Rust is especially unforgiving for people who have a style that relies on writing something and seeing if it complies, rather than integrating a mental model of the language so you can work with it instead of against it. In this case, if someone reaches a point where they’re so frustrated that they have to remember the basic rules of the language, why even force yourself to use that language? There are numerous other languages that have lower mental overhead, garbage collection, relaxed type rules, and so on in ways that match different personalities better. Forcing yourself to use a language you hate isn’t a good way to be productive.
- dwattttt 2y ago> A theme I’m noticing more frequently as Rust gains popularity is people trying to use Rust even though it doesn’t fit their preferred way of coding. I could've expressed the sentiment in this blog post back when I started playing with Rust ~2016. Instead, I ended up learning why I couldn't pass a mutable reference to a hashmap to a function I'd looked up via that hashmap (iterator invalidation lesson incoming!). The kind of bug I was trying to add exists in many languages. We can only speak in general terms about the code the blog post is talking about, since we don't have it, but couching it in terms of "doesn’t fit their preferred way of coding" misses that the "preferred way of coding" for many people (me included) involved bugs I didn't even realise could exist.
- Const-me 2y ago
- nu11ptr 2y agoLike many of these sorts of critique articles, I can see the author's pain point and empathize, but don't fully agree. Yes, it is true that if you aren't careful you can end up with a design that doesn't work and has to be redesigned. And yes, I do agree when this happens it can be very frustrating (and a time suck). However, in my experience, and it probably depends what type of code one writes, it doesn't happen enough to fully mar the experience of an otherwise very productive language. If I had to guess, I would say this happens to me maybe 1 day in 30. Not great, but not catastrophic either. If I'm working on a section of code the relies heavily on borrowing and lifetimes, I will typically work up a prototype without all the functionality just to ensure I have a workable design before going back to fill in the rest of the code. This is probably why I don't tend to hit it all that often. It would be ideal if this wasn't necessary, but Rust has all sorts of other awesome features that make this something worth enduring.
- fpgaminer 2y agoAs a long time Rust user, I more or less agree that it's a pain. And yeah, when working on a big project refactoring code, it's difficult to know ahead of time if the borrowing pattern you _think_ will work will actually work. Of course, that's always the trade off with Rust. You're trading a _lot_ of time spent up-front for time saved in increments down the road. As a concrete example, I'm in the process of building up a Rust database to replace the Postgres solution one of my applications is using. Partly because I'm a psycho, and partly because I've gotten query times down from 20 seconds on Postgres to 50ms with Rust (despite my best efforts to optimize Postgres). Being a mostly ACID, async database, this involves some rather unpleasant interactions with the Rust borrow checker. I've had to refactor a significant portion of the code probably five times by now. The lack of feedback during the process is a huge pain point, as the article points out (though I'm not sure what the solution to that would be). Even if you _think_ you know the rules, you probably don't, and you're not going to find out until 2 hours later. The second most painful point has to be the 'static lifetime, which comes up a lot when dealing with threading and async. For me it's when I need to use spawn_blocking inside an async function. Of course, the compiler has no way of knowing _when_ or _if_ spawn_blocking will finish, so it needs any borrows to be 'static. But in practice that means having to write all kinds of awkward workarounds in what should otherwise be simple code. I certainly understand the _why_, and I'm sure in X years it'll be fixed, but right now ... g'damn. That said, the borrow checker _has_ improved. I think my last major Rust project was before the upgraded borrow checker, which wasn't able to infer lifetimes for local variables. So you had to throw a lot of stuff inside separate blocks. We also have a lot more elided lifetimes now. Just empirically from this project I'd say me and the borrow checker only had about 30% of the fisticuffs we did in the past. Personally, I think the tradeoff is worth it. It won't be for everyone, or every project. But 20s to 50ms query time, with a ton of safety guarantees to ensure the valuable data running through the database is well cared for? Worth every line of refactored code. Asides: * The project in question: https://github.com/fpgaminer/tagstormdb https://github.com/fpgaminer/tagstormdb * I also replaced some of my large JSON responses with FlatBuffers. FlatBuffers is a bit of a PITA, but when you're trying to shuffle 4 million integers over to the webapp, being able to do almost 0 decoding on the browser side, and get them as a Uint32Array directly is gold. * It's a miracle I got away with the search parser in the project. I use Pest, and both the tree it spits out and the AST I build from it hold references. Yet sprinkling a little 'a on the impl's and struct's did the trick. * Dynamic dispatch has also improved, as far as I can tell, which used to always involve some weird lifetimes if the return values needed to borrow stuff. * ChatGPT o1 is a lot better at Rust then 4 or 4o. I've gotten a lot more useful stuff out of o1 this time around, including less hallucinations. Still weaker than Python/TypeScript/etc, with maybe 2-3 compile errors that need to be fixed each time. But still better. Sonnet completely failed every time I tried it :/ (Both through Copilot and the web). o1 in Copilot _could_ be amazing, since I can directly attach all my code. But the o1 in Copilot _feels_ weaker. I'm fairly sure the 4o Copilot uses is finetuned, and possibly smaller, so it too always felt weaker. Seems like o1 is the same deal. Still really useful for the Typescript side of things, but for Rust I had to farm out to the web and just copy paste the files each time.
- georgelyon 2y agoI’ve been wrestling with Swift’s region isolation checker recently and had a similar experience.
- liontwist 2y agoDaily reminder that every Turing complete language has undefined behavior.
- emtel 2y agoWhat? That’s not true. Turing machines themselves do not have undefined behavior.
- liontwist 2y agoHalting problem? The overall point is it’s impossible to make a perfect spec. Rust doesn’t even have a spec, so your behavior is whatever the compiler gives you, which is C++ UB too.
- MathMonkeyMan 2y agoUndefined behavior means something specific in the language specification world. The language specification, or standard, guarantees certain things about the behavior of programs written to the specification. "Undefined behavior" means "you did something such that the guarantees of this specification no longer apply to your program." It's pretty much a worst case scenario in terms of writing programs. The program might do... anything. Fortunately, in reality it happens all the time and programs often keep behaving close enough to what we expect. Turing completeness is unrelated to that sense of "undefined behavior".
- liontwist 2y agoI understand and my point is rust has no “ub” only because there is no spec, not because it avoids inherent computing problems. > halting They are not entirely unrelated. C++ UB is often things that would be very difficult to detect. For example infinite template recursion is undefined. Specifying any other behavior is impossible due to halting problem. Another example: a system might be able to detect out of bounds pointer deref, or maybe not. Same with signed integer overflow.
- bitbasher 2y agoI am currently learning Rust and found the post interesting. As I learn the language, I keep reading how Rust is great and you don't have to manage memory (unlike C or C++). However, managing ownership and lifetime _is_ managing memory. The borrow checker is there, all the time, reminding you of memory management. Now, in C and C++ the same problem exists but you don't have a borrow checker to remind you. I think this is the same conclusion the blog post came to, but I'm not entirely sure.
- chrismorgan 2y ago> This means, to be a highly productive Rust programmer, you basically have to memorize the borrow checker rules, so you get it right the first time. This is stupid, because the whole point of having a type system or a borrow checker is to tell you when you get it wrong, so you don’t have to memorize how the borrow rules work. This is completely back to front. Of course you have to internalise the rules of a borrow checker or type system to be highly productive. How can you hope to do a good job without that?
- eviks 2y agoYou can do a good job by offloading that cognitive overhead to better tooling
- bombela 2y agoWhat if Rust itself is the better tooling?
- audunw 2y agoIs it just me or is everyone in this comment talking past each other, with half of them not really understanding what the article is complaining about? My take-away was that the article concludes that Rust is NOT a good tool for working with the borrow checker. I don’t think it said that there’s anything wrong with a borrow checker, and that you shouldn’t learn the basics of how it works or how to write idiomatic Rust. A good tool shouldn’t require you to have a perfect memory of all the rules for you to be highly productive with it. If you make mistakes it should quickly tell you so with a message that quickly lets you figure out what to change. I think this stands in contrast with Zig where these goals is the highest priority of the language. It’s also very strict with little to no undefined behaviour. But there’s also a lot of discipline in not introducing syntax or semantics that makes it hard for the compiler/checker to give a quick pass/fail with a clear message about went wrong. You can see from the issues in GitHub that improving error messages for failures in the type system is consistently prioritised. That puts a hard constraint on Zig where they’re held back from putting too much power into the type system. That’s not to say that Rust doesn’t prioritise being a good tool. But the semantics of borrow checking makes their job an order of magnitude more difficult here. It’s an inherent trade-off. They’ve made a huge jump in the complexity and power of the language, and it’s probably much harder to then make a tool that makes it comfortable to work with this kind of power. Some here may find it easy to deeply understand all the rules and to write code the first time that doesn’t trip up Rust too much. But in the real world code is written by many different kinds of people with different kinds and levels of intelligence. I’ve found this to be an important consideration when choosing languages, libraries, tools and methodologies for large teams. One way to be a 10x programmer is to write 10x as much code as an average programmer. Another way is to make 10 other average programmers twice as efficient, and that’s clearly more scalable. Rust may still be a good choice to make the team more productive in the long run. My point is that adopting it in a team should perhaps not be considered a trivial decision.
- Sytten 2y agoPeople seriously need to stop obsessing about RC/ARC. Just use it, it will be fine. The perf difference wont matter in 99.99% of the cases. Whole languages (Swift) are based on that.
- wavemode 2y agoThis persons's problem is pretty clear - Rust is frankly miserable to write code in if you are trying to optimize everything as much as possible. Since this is the default mindspace of C/C++ programmers, the frustration is understandable. Rust becomes a lot simpler when you borrow less and clone more. Sprinkle in smart pointers when appropriate. And the resulting program is probably still going to have fantastic performance - many developers err by spending weeks of developer time trying to shave off a few microseconds of runtime. But, if you're a developer for whom those microseconds really do matter a lot, well then you just have to bite the bullet.
- jandrewrogers 2y agoI think I come down somewhere close to this. Idiomatic safe Rust is noticeably slower and less scalable than the usual high-performance systems code many C++ developers write. This puts them in the position of either abandoning performance they know is possible or abandoning Rust code that is safe and sane, neither of which feels good. Anecdotally, all of the happy productive Rust programmers I know do not come from a hardcore systems background. They were mostly Java and Python developers that wanted to get a bit closer to the metal. For them it is probably a great experience, and the performance is an improvement relative to what they are accustomed to. There are quite a few shops that use Rust and C++ together, often wrapping a C++ core (for performance) with a Rust API layer (for safety).
- jltsiren 2y agoThat doesn't match my experience. C++ programmers typically use C++, because using libraries written in C++ from any other language is a miserable experience. It's rare to encounter C++ code with extensive low-level optimizations. If you are used to C++11 or newer, you should be able to continue writing very similar code in Rust. The only major issue I encountered was the lack of the idea that "because objects A and B have effectively the same lifetime, they can safely store references to each other, as long as..." But if you are used to older versions of C++, trying to write similar code in Rust is going to be painful.
- olvy0 2y agoThis is exactly what I thought about rust when trying to learn it a few years ago. I'm also an experienced C++ programmer. After trying for 3-4 months and constantly fighting the borrow checker, I lost all motivation and gave up.
- goku12 2y agoRust borrow checker rules are a bit weird and unintuitive. But if you are a systems programmer (C or C++) and think a bit about the borrow checker complaints, you'll find that they almost always correspond to memory safety bugs like use-after-free or invalidated references. All you need to think is about what might happen if the code was accepted (this is for C/C++ programmers, since GC-based language programmers don't face those often). The same mistakes can happen in C and C++ too - but without the BC to back you up. In essence, there is no escape from those exact same rules. There are a few genuine cases which the BC won't accept, though they may be valid. The first case is of data structures containing cycles (like dequeues, ring buffers, closed graphs, etc). The other is cross-FFI calls. This is due to the fact that the BC simply doesn't have the intelligence to reason about them (at compile-time). Even then, Rust gives you 2 types of escape hatches - runtime safety checks (using Rc, Cell, etc) with a slight performance impact, and manual safety checks (unsafe) when performance is paramount. All that's expected of you (the programmer) is to recognize such cases and use the appropriate solution. I'm not too surprised when non-C/C++ programmers struggle with BC rules. They may be unfamiliar with the low-level execution semantics. But I'm surprised when C/C++ programmers fail to make the connection. I was a C++ programmer too and this is the first thing I noticed. Memorizing the BC rules is the absolute worst way to learn it. You should be looking for memory safety problems and correlating them with BC error messages instead. I know this works because I trained non-systems (non-C/C++, primarily JS and Python) developers in Rust. They picked up the execution and memory semantics quickly and easily made sense of the borrow checker idiosyncrasies.
- deleted 2y ago[deleted]
- melodyogonna 2y agoIt's interesting how Mojo solves some of Rust's lifetime UX issues. Because Mojo values uses ASAP destruction rather scope-based destructor, what Mojo's lifetime has to do is correctly track the last place a value was used, it doesn't track the validity of a scope. What this means in practice is that Mojo's lifetime checker extends the life of values. Just point it at an origin and it'll ensure the origin is still alive wherever you use the value attached to it. It completely defines away "value does not live long enough" compiler problems.
- Animats 2y agoRust's reference topology is too restrictive. You can't have back references. This is what drives many C++ programmers nuts. It's common in C++ to have A point to B, and for B to have a pointer back to A. This happens implicitly with class inheritance, too. As a result, common C++ idioms don't translate to Rust at all. This is fixable. Because you can have back references. You just have to use Rc, Rc::Weak, .upgrade(), RefCell, .borrow, and .borrow_mut(). This works, but only if the upgrades and borrows never fail. A failed .borrow() is a panic. The implication is that if you use .borrow() or .borrow_mut(), there's some good reason to think it will never fail. For Rc::Weak, the key constraint is that all weak pointers must drop before all strong pointers have dropped. If you can prove that, .upgrade() doesn't need a run-time check. For RefCell, the key constraint is that no .borrow() or .borrow_mut() may be enclosed by the scope of a conflicting .borrow() or .borrow_mut(). This requires a transitive closure check on who borrows what. For many simple cases, this is statically checkable. It does require inter-function checking. Can those checks be moved to compile time? Probably. There's already a compile-time static Rc.[1] Compile-time RefCell checking looks possible.[2] It's non-trivial to do this, but worth thinking about. DARPA's TRACTOR project (Translating All C To Rust) is likely to generate vast amounts of Rc-heavy code, if it works. So that provides some motivation for doing something to check at compile time. [1] https://github.com/matthieu-m/static-rc https://github.com/matthieu-m/static-rc [2] https://internals.rust-lang.org/t/zero-cost-interior-mutability-proposal/21814 https://internals.rust-lang.org/t/zero-cost-interior-mutabil... [3] https://www.darpa.mil/program/translating-all-c-to-rust https://www.darpa.mil/program/translating-all-c-to-rust
- oconnor663 2y agoI don't love Rc as a cure for borrow checker woes. When Rust programs need cyclic, graph-y things like back references, I almost always suggest moving to indexes: https://jacko.io/object_soup.html https://jacko.io/object_soup.html. You can get fancy and reach for a SlotMap or a whole ECS, but a lot of simple cases can get away with just Vec. (And even complex cases can use HashMap if performance isn't critical.)
- Animats 2y ago> moving to indexes Then you can have dangling pointers that point to the wrong place. Race conditions become possible. You need an allocator for the indices. I've had problems with a renderer that works that way. On rare occasions, it crashes with an invalid index. Even with Rust safe code.
- sfink 2y agoI'm a relative beginner at Rust, but this matches my experience fairly well. Especially the part about the brittleness, where adding just one little thing can require propagating changes throughout a project. It might be adding lifetimes, or switching between values and references, or wrapping things in Rc or Arc or RefCell or Box or something. It seems hard to do Rust in a fully bottom-up fashion; you'll end up having to adjusting all the pieces repeatedly as you fit them together. Maybe there's a style I haven't learned yet where you start out with Arc everywhere, or Rc, or Arc<Mutex<T>>, or whatever, and get everything working first then strip out as many wrappers as possible? It just feels wrong to go around hiding everything that's going on from the borrow checker and moving the errors to runtime. But maybe that's what you need to do to prototype things in Rust without a lot of pain? I don't know enough to know. I have already noticed that building up the mindset of figuring out your ownership story, where your values are being moved to all the time, is addictive and contagious -- I'm sneaking more and more Rusty ways of doing things into my C++ code, and so far it feels beneficial.
- eddd-ddde 2y ago> Maybe there's a style I haven't learned yet where you start out with Arc everywhere, or Rc, or Arc<Mutex<T>>, or whatever, and get everything working first then strip out as many wrappers as possible? I wouldn't recommend that. It's easy to end up with a fundamentally flawed architecture impossible to refactor out of. In general as long as you stick to keeping data ownership as high up in the call stack as possible everything should slowly fall into place. Think functional core imperative shell. Your main has services, dependencies, data, and just makes calls that operate on data without trying to make deeper owned objects that are inherently hard to keep references to.
- kstrauser 2y agoAgreed. IMO, anywhere you butt heads with the borrow checker is a place where you’d have to by hyper nitpicky about user after free or memory leaks in C or C++, just without the compiler shouting at you to fix it.
- oconnor663 2y ago
- thaumasiotes 2y agoA tangential issue: > Unfortunately, it can only catch undefined behavior that actually happens, so if your test suite doesn’t cover all your code branches you might have undefined behavior lurking in the code somewhere. Covering every branch is not enough to say you have full coverage. if( condition1 ) { /* complicated things */ } if( condition2 ) { /* complicated things */ } if( condition3 ) { /* complicated things */ } There are only three branches and you can "cover" them all with six tests. (Heck - you can cover them all with just two tests!) But there are eight paths through the code, and six tests can cover at most six of them.
- chipdart 2y agoFrom the article: > This is painful because I am an experienced C++ programmer, and C++ has this exact problem except worse: undefined behavior. In the worst case, C++ simply doesn’t check anything, compiles your code wrong, and then does inexplicable and impossible things at runtime for no discernable reason (or it just deletes your entire function). This is completely wrong, even in the "not even wrong" territory. It reads like an attempt to parrot a cliche without having any idea what it means. "Undefined behavior" just means the standard does not define what is the expected behavior, and purposely leaves implementations free to implement it how they see fit. This means crashing the app or sending an email to the pope. In practical terms this means developers should not write code that triggers undefined behavior, and treat the code that does as errors requiring a fix. Advanced users can lean on implementation-defined behavior from compilers to add some expectation to the behavior, but that's discouraged. It's so strange how someone calling themselves a seasoned C++ developer fails to understand such a basic aspect of the language. The important tidbit is that a) it's completely wrong to parrot "undefined behavior" on "C++ doesn't check anything", and b) if you code triggers undefined behavior without your knowledge then you just broke the code and wrote a bug out of your own ignorance. To make matters worse, there are a myriad of code checkers for C++ that catch undefined behavior and even some classes of safety errors. Take for instance cppcheck. Why is the blogger whining about undefined behavior and "c++ not checking" when adding cppcheck to any project is enough to detect most if not all cases?
- throw_a_grenade 2y agoThe quote is incomplete without its continuation: > This means that in order to write C++, you effectively have to memorize the undefined behavior rules, which sucks. Sound familiar? Which is the point TFA is making. I believe you expended the attention span a bit too early.
- chipdart 2y ago> Which is the point TFA is making. Again, the point is wrong in more than one way. You don't simply add undefined behavior. It's wrong, and buggy code. Onboard a static code analysis tool like cppcheck to tell you when you messed up. It takes far less work to onboard any of these tools than it takes to write a sentence in a blog post.
- dathinab 2y agoI habe been writing rust at work since 2016 or so and I can't say that the virtue checker ever had been much of a problem. Like it's not that it never caused issues but most times fixing them also produces much better code. In my experience the most common place to run into issues is if you write C/C++ style code in rust. Or if you write certain kinds of functional style code in rust, rust has many functional features but isn't strictly speaking a functional language and while some functional pattern work well many other fall apart especially if combined with async (which will get better once async closure are stabilize). in the end it often boils down to trying to use patterns and styles for other languages in a language which doesn't support them well, which always causes issues, but most times (in other languages) in more subtle ways then compiler errors, e.g. UB, perf, etc. Through there is one field (game programming) where as long as your project doesn't become quite big you can get away with a lot of suboptimal approaches of state handling but not in rust. So if it comes to hobby from scratch state game programming I wouldn't be surprised for people to get annoyed (but if it's game programming using existing frameworks and e.g. stuff like a entire component library like bavy it's a different topic altogether)
- goku12 2y agoIf anyone's just starting out with Rust and struggling with the borrow checker, here are some tips. I have been using Rust since 2013 and have tried these ideas myself and while training others. (C/C++ devs may be familiar with some of these and may skip them.) 1. Give priority to the fundamental semantics of code execution - things like C memory layout [1], function calls, stack, stack frames, frame invalidation, heap, static data, multi-threading/programming, locks, synchronization, etc. Also learn associated problems like double-free, use-after-free, invalidated references, data races, etc. These are easiest to understand if you learn an assembly language. However, if you don't have the time or patience for that, at least focus hard on the hardware and memory layout topics in Rust books. If you prefer instead to learn those by making mistakes without the borrow checker intervening, start with C. (Aside: This knowledge is needed for C and C++ as well, especially C. You can't write large code without it.) 2. DON'T try to memorize the borrow checker. Borrow checker is based on a few simple principles (which you should know), but they can manifest in very complex and surprising ways (same as memory safety bugs). It's not practical to learn all such cases. Instead, check the borrow checker error message and see if you can correlate it to any memory safety problems I mentioned above. While the borrow checker can seem very complicated and arbitrary, it's designed solely to prevent those memory safety bugs. Pretty soon, you'll be comfortable with correlating BC errors to such bugs, without having to worry about how the BC found them. Knowing the real problem will also make it easy to satisfy the BC and avoid fighting with it. 3. Understand the memory layout of data structure that you use. Ref: [2]. Borrow checker errors often require this knowledge to make sense. The same becomes crucial during debugging if you make such mistakes in C/C++. The BC wont even allow you to compile it if you make similar mistakes in Rust. You need it just to get the program to run. 4. Borrow checker wont solve every problem for you. It doesn't have the intelligence to reason it all. There are a few notable cases: - Data structures with cycles (like closed graphs, dequeues, etc) and algorithms that deal with them. (BC prefers data structures in a tree hierarchy) - Function calls across an FFI boundary (since the BC can't check that code) - Valid cases according to borrow checker rules, but rejected anyway due to complicated lifetime analysis. These may eventually get resolved in a later Rust version. But such cases exist. 5. Most of the BC errors can be solved by simple code refactors. But in cases like above, you need to identify them (as a limitation of the BC) and look for an alternative solution. BC is a compile-time safety checker. The alternatives are: - Runtime safety checks using concepts like Rc, Arc, Cell, RefCell, etc. They will pass the BC checks at compile time. But if memory safety is violated at runtime, it will simply panic and crash. It may also have a slight performance hit due to runtime checks. But this is most often very negligible, given the fact that most other languages are based entirely on such checks (GC, ARC, etc). You don't need to be too shy in resorting to these to get around BC complaints. Many Rust programmers do. - Manual safety checks using unsafe. If the performance is absolutely important for you, you can use unsafe functions and blocks. Unsafe keyword activates some potentially memory-unsafe language features (like raw pointer de-referencing) that the BC doesn't vet (Note: BC is not deactivated). This is often what you need when you're trying to implement an unavailable data structure or algorithm. This is also the only choice for FFI calls. Rust will not check them at any time. But this usually isn't a problem. Unsafe blocks are often used only for very fundamental ideas (eg: self-referential structs) and consist of no more than 5% of a codebase. If a memory safety bug does occur, it will be in one of those blocks and will be easier to locate and correct. Moreover people convert them to libraries and publish them on crates.io. This improves the chance of finding any hidden bugs. If you need unsafe code, there's probably a library for it already. To get a better intro, check out 'Learn Rust With Entirely Too Many Linked Lists' [3] [1] https://www.scaler.com/topics/c/memory-layout-in-c/ https://www.scaler.com/topics/c/memory-layout-in-c/ [2] https://cheats.rs/#basic-types https://cheats.rs/#basic-types [3] https://rust-unofficial.github.io/too-many-lists/ https://rust-unofficial.github.io/too-many-lists/
- wduquette 2y agoThis tallies with my experience with Rust. Four years ago I wrote an implementation of the TCL language in Rust (see https://github.com/wduquette/molt https://github.com/wduquette/molt). It uses no unsafe code, and includes enough of the language to be useful. But it isn’t terribly efficient, and it’s a bit of a memory hog, and so I started looking at ways to improve it. I usually like to evolve a code base towards a new architecture a little at a time, keeping it running and passing tests at every step of the way. What I found was that even seemingly small changes required an awful lot of work, as the OP says; if I could make them work at all. Eventually I decided that I’d learned what I’d needed to, and walked away from it. (To be fair, this was late spring or early summer of 2020, everything was peculiar, and I didn’t have the spare mental capacity for the project.) I should add: I understand the need to use a language the way it wants to be used, and that you need to assimilate and internalize that to be truly fluent. I concluded that I didn’t need Rust’s extreme performance for the kind of work I do, and that there are less intrusive ways of getting memory safety.
- kazinator 2y agoIt sounds like a lot of brain cycles for the sake of restricted form of memory management that cannot handle a simple graph structure. We have solved memory management; you can connect your objects every which way and garbage collection can deal with it. We should use that for 99.9..% of the code that's out there. The rest could as well be written in assembler, except that we have multiple instruction sets to target.