Most active commenters
  • kstrauser(12)
  • chipdart(12)
  • sfink(8)
  • goku12(7)
  • estebank(6)
  • (5)
  • maxbond(5)
  • Arch-TK(5)
  • Const-me(5)
  • dwattttt(4)

177 points signa11 | 239 comments | | HN request time: 2.284s | source | bottom
1. capitol_ ◴[] No.42145594[source]
This 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".

2. pornel ◴[] No.42145864[source]
True, 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.

3. mjevans ◴[] No.42160806[source]
This 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.

replies(4): >>42160836 #>>42160837 #>>42160849 #>>42160939 #
4. rokob ◴[] No.42160809[source]
I 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.

replies(1): >>42160841 #
5. foxhill ◴[] No.42160822[source]
what 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.

6. kstrauser ◴[] No.42160831[source]
Rust 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.

replies(3): >>42160844 #>>42161027 #>>42161181 #
7. danpalmer ◴[] No.42160836[source]
Go’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.
8. kstrauser ◴[] No.42160837[source]
Rust’s checks are also evaluated and enforced at compile time.
9. kstrauser ◴[] No.42160841[source]
Same for me. I think I write better code in other languages now.
10. maguirre ◴[] No.42160844[source]
Are there examples one can learn from about idiomatic rust? I would appreciate either books or projects to learn from.
replies(3): >>42160890 #>>42160891 #>>42161001 #
11. hypeatei ◴[] No.42160849[source]
Dependencies 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

replies(2): >>42160871 #>>42162412 #
12. sestep ◴[] No.42160860[source]
It 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.
replies(1): >>42160870 #
13. dwattttt ◴[] No.42160870[source]
borrow checking relies on types to be able to check; types are what carry borrows after all.
replies(1): >>42160918 #
14. saghm ◴[] No.42160871{3}[source]
It'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.
15. amelius ◴[] No.42160877[source]
Yes, and people should stop using Rust for projects that do not require a systems programming language.

Know what tool to pick.

replies(4): >>42160896 #>>42160922 #>>42161000 #>>42163093 #
16. James_K ◴[] No.42160879[source]
An 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.
replies(1): >>42163269 #
17. lowbloodsugar ◴[] No.42160887[source]
My 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”.

18. kstrauser ◴[] No.42160890{3}[source]
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.

19. galangalalgol ◴[] No.42160891{3}[source]
Rust, 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.
replies(3): >>42161004 #>>42161039 #>>42165146 #
20. kstrauser ◴[] No.42160896[source]
I used it for an API server and it was pleasantly fun. “Systems” is a broad brush.
21. melvyn2 ◴[] No.42160909[source]
The 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.
replies(3): >>42161088 #>>42161232 #>>42162609 #
22. sestep ◴[] No.42160918{3}[source]
This is true but misleading: analogously, typechecking depends on parsing, but IDEs typically make a best effort to typecheck syntactically ill-formed programs.
replies(1): >>42161044 #
23. eYrKEC2 ◴[] No.42160922[source]
It's a high level language with algebraic data types and fantastic type checking. I'll use it where ever I darn well please.
replies(1): >>42160951 #
24. jchw ◴[] No.42160937[source]
> 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.

replies(3): >>42161072 #>>42161147 #>>42161879 #
25. maxbond ◴[] No.42160939[source]
I 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?

replies(1): >>42161950 #
26. Arch-TK ◴[] No.42160944[source]
I 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.

replies(7): >>42161052 #>>42161225 #>>42161510 #>>42162166 #>>42162494 #>>42162555 #>>42162621 #
27. skp1995 ◴[] No.42160949[source]
Rust 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.

replies(2): >>42160986 #>>42162509 #
28. runeblaze ◴[] No.42160951{3}[source]
Sometimes I write Rust with Arc and Rc sprinkled everywhere because I just need a dialect of OCaml.
replies(1): >>42161177 #
29. ufmace ◴[] No.42160966[source]
I 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.
replies(1): >>42161279 #
30. throwaway81523 ◴[] No.42160971[source]
> 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.

31. estebank ◴[] No.42160986[source]
> 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.

replies(1): >>42161031 #
32. maxbond ◴[] No.42161000[source]
Sure. But pick based on concrete knowledge of the tool rather than broad and abstract categories. Tools are routinely discovered to be more broadly applicable than their initially intended use case, or even to be poor fit for that use case. The map is not the terrain.

And in any case, ecosystem usually trumps other concerns. Deep learning is a task you'd think a system language would be ideal for. Yet Python is a probably a better choice than Rust. The best reason to avoid Rust for a new project is probably that the market for Rust developers is insufficiently liquid (though presumably that won't be true for much longer, if it's still true at all).

33. zeta0134 ◴[] No.42161001{3}[source]
Generally I have the easiest time when I declare my state in the outermost scope possible, and then pass it into functions that need to operate on it. If I'm using an actual pointer, rather than a mutable reference that came in as an argument, something weird is happening! Usually that's the interface with some external library.

Rust in particular is *really* obnoxiously bad at OOP patterns, and I think my lesson at this point is that this is because it is hard to do OOP safely, at least in a way that jives with its borrow checker. Something like functional core, imperative shell seems to be a much nicer flow for the thing in general.

Anyway, I've just got the one major Rust project (an NES emulator) so I'd say I'm pretty early in my Rust journey. For me personally, the good points (delightful match, powerful enum) outweigh the bad (occasional borrow checker weirdness, frustrating lifetimes) but I think it depends a lot on what you're trying to do with it.

replies(1): >>42161062 #
34. lowbloodsugar ◴[] No.42161004{4}[source]
I 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.

35. Ygg2 ◴[] No.42161017[source]
> 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.

replies(1): >>42161219 #
36. nicce ◴[] No.42161027[source]
I learned Rust before learning C properly.

Oh boy. I see bugs everywhere in C and why the borrow checker exists. It really forces you to understand what happens under the hood.

The most issues in Rust are indeed related the expressions - you don't know how to describe some scenario for compiler well-enough, in order to prove that this is actually possible - and then your program won't compile.

In C, you talk more to the computer with the language syntax, whereas in Rust you talk to the compiler.

replies(3): >>42161173 #>>42161240 #>>42162370 #
37. skp1995 ◴[] No.42161031{3}[source]
will 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.

replies(1): >>42161143 #
38. nh2 ◴[] No.42161039{4}[source]
It's difficult to really use Rust purely functionally given that it removed pure functions from its type system, and that has a limited stack size.
39. estebank ◴[] No.42161044{4}[source]
rustc 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.

40. akira2501 ◴[] No.42161052[source]
> 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.

41. dinobones ◴[] No.42161053[source]
I'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.

replies(2): >>42161108 #>>42164582 #
42. ijustlovemath ◴[] No.42161062{4}[source]
You can achieve some level of OO design by using traits (the generic kind, not the dyn kind), but I think the functional style and inline testing gives you a ton of nice properties for free!

Rust also pushes you to refactor in a way that really pulls out the core of your problem; the refactoring is just you understanding the problem at a deeper level (in my experience)

43. troad ◴[] No.42161064[source]
I 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.

replies(7): >>42161186 #>>42161243 #>>42161255 #>>42162270 #>>42162593 #>>42162641 #>>42162713 #
44. maxbond ◴[] No.42161072[source]
There is no answer. There are only improvements.
45. FridgeSeal ◴[] No.42161073[source]
Can'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.

replies(2): >>42161104 #>>42161413 #
46. Pannoniae ◴[] No.42161088[source]
Fair 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)

replies(3): >>42161120 #>>42161179 #>>42161194 #
47. skp1995 ◴[] No.42161104[source]
I 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

48. Aurornis ◴[] No.42161106[source]
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.

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.

replies(4): >>42161149 #>>42161234 #>>42162570 #>>42164055 #
49. loeg ◴[] No.42161108[source]
I think OP is likely overusing references. The vast majority of code doesn't need to deal with explicit lifetimes.
replies(1): >>42161184 #
50. ◴[] No.42161120{3}[source]
51. nu11ptr ◴[] No.42161136[source]
Like 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.

52. estebank ◴[] No.42161143{4}[source]
If 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.

53. tialaramex ◴[] No.42161147[source]
Two 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.

54. dwattttt ◴[] No.42161149[source]
> 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.

replies(3): >>42161208 #>>42161209 #>>42162462 #
55. kanbankaren ◴[] No.42161173{3}[source]
> Oh boy. I see bugs everywhere in C and why the borrow checker exists.

Any examples that you could provide? I have been dealing with C/C++ for close to 30 years. Number of times I have shot myself with undefined/unspecified behavior is less than 5.

replies(5): >>42161886 #>>42162308 #>>42162360 #>>42162444 #>>42164671 #
56. estebank ◴[] No.42161177{4}[source]
The only thing that's really missing is deref patterns so that you can pattern match on an Arc<T> with a T pattern.
replies(1): >>42165742 #
57. kstrauser ◴[] No.42161179{3}[source]
I 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.
58. Aurornis ◴[] No.42161181[source]
> Rust was a pain in the ass until I stopped trying to write C code in it and started writing idiomatic Rust.

This is the #1 problem I see with people trying to learn a new language (not just Rust).

I’ve watched enough people try to adopt different languages at companies over the years that I now lean pessimistic by default about people adopting new languages. Many times it’s not that they can’t learn the new language, it’s that they really like doing things the way they learned in another language and they don’t want to give up those patterns.

Some people like working in a certain language and there’s nothing wrong with that. The problems come when they try to learn a new language without giving up the old ways.

Like you, I’m getting similar vibes from the article. The author wants to write Rust, but the entire premise of the article is about not wanting to learn the rules of Rust.

replies(5): >>42161933 #>>42162613 #>>42163170 #>>42167602 #>>42170866 #
59. andybak ◴[] No.42161184{3}[source]
OP sounds fairly smart and my first thought is "if OP is struggling then Rust probably isn't for me".

Who is Rust for?

replies(2): >>42161303 #>>42162100 #
60. NoboruWataya ◴[] No.42161186[source]
No 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.
replies(1): >>42161361 #
61. fpgaminer ◴[] No.42161187[source]
As 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

* 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.

replies(1): >>42162397 #
62. Aurornis ◴[] No.42161194{3}[source]
Rust 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.
replies(1): >>42162391 #
63. Const-me ◴[] No.42161208{3}[source]
> The kind of bug I was trying to add exists in many languages

Any example except C++? BTW, the closest thing possible in C# is modifying a collection from inside foreach loop which iterates over the collection. However, standard library collections throw “the collection was modified” exception when trying to continue enumerating after the change, i.e. easy to discover and fix.

replies(3): >>42161262 #>>42161962 #>>42162160 #
64. AlienRobot ◴[] No.42161209{3}[source]
I'm pretty sure most languages just make the reasonable assumption that you want to map to the object so it uses the pointer for hashing and not actually hash its value.
replies(1): >>42161275 #
65. recursivecaveat ◴[] No.42161219[source]
They 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.
replies(1): >>42163261 #
66. tialaramex ◴[] No.42161225[source]
The 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.

replies(2): >>42162516 #>>42163562 #
67. Spivak ◴[] No.42161232[source]
If the borrow checker only errd on code with bugs you could call it learning. Or if it was only possible to express correct programs in the Rust language. But such a thing isn't possible in general so we accept the weaker condition, accepting a subset of all valid code that can be proven correct. The usability of the language goes with how big that subset is, and the OP is expressing frustration at the size of Rust's.

Rust isn't alone in this, languages with type hints are currently going through the same thing where the type-checker can't express certain types of valid programs and have to be expanded.

68. SoftTalker ◴[] No.42161234[source]
> you’re not yet at the point where everything clicks

This just seems to be the standard excuse I read any time someone has a critique of Rust.

replies(1): >>42162683 #
69. marcosdumay ◴[] No.42161240{3}[source]
> In C, you talk more to the computer with the language syntax, whereas in Rust you talk to the compiler.

The C compiler pretends to be the computer. But UB is still there, as a compiler-only thing that has no representation at all on the computer.

70. __float ◴[] No.42161243[source]
I can't help but feel this is a somewhat veiled complaint about the Rust community instead of anything substantive with the language :/
replies(1): >>42161318 #
71. kstrauser ◴[] No.42161255[source]
I am A-OK with someone not liking Rust. I do, but it’s still only my 3rd-most used language behind the Python and TypeScript I write at work.

It’s just that time after time I’ve heard people criticize Rust because they were, in fact, trying to write their pet language in Rust. It’s similar to how many complaints I’ve heard about Python because “it’s weakly typed”. What? Feel free not to like either of them, but make it for the right reasons, not because of a misunderstanding of how they work.

Now, the author of this post may be doing everything right and Rust just isn’t good at the things they want to use it for. The complaint about constantly bumping against the borrow checker leads me to wonder.

replies(1): >>42161390 #
72. dwattttt ◴[] No.42161262{4}[source]
> modifying a collection from inside foreach loop

This is exactly what the mutable map pointer was for, for the function to be able to modify the collection; C++ would result in potentially iterating garbage, C# it sounds like would throw an exception (and so show the design wouldn't work when I tried to test it), Python definitely didn't do a graceful thing when I tried it just now. And if I had a collection struct in C, I'm sure I could've done some horrible things with it when trying.

The best of those outcomes is C#, which would've shown me my design was bad when I ran it; that could be as early as the code was written if tests are written at every step. But it could be quite a bit later, after several other interacting parts get written, and you rely on what turns out to be an invalid assumption. For Rust, the compiler alerted me to the problem the moment I added the code.

FTR I ended up accumulating changes to the map during the loop, and only applying them after the loop had finished.

EDIT: Python did do something sensible: I didn't expect pop'ing an element from the list to echo the element popped in the REPL, and got a printed interleaved from front to back, which does makes sense.

replies(1): >>42162386 #
73. ◴[] No.42161275{4}[source]
74. Const-me ◴[] No.42161279[source]
> 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.

replies(2): >>42161511 #>>42165016 #
75. kstrauser ◴[] No.42161303{4}[source]
In 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.
76. georgelyon ◴[] No.42161314[source]
I’ve been wrestling with Swift’s region isolation checker recently and had a similar experience.
77. troad ◴[] No.42161318{3}[source]
Veiled? :P

I'd characterise it as a gentle criticism of the way the Rust community tends to react to anything other than effusive praise.

Rust is a nifty language, albeit with room for improvement, that falls into the (sadly overpopulated) category of 'neat thing, somewhat obnoxious fan club'.

replies(1): >>42163210 #
78. troad ◴[] No.42161361{3}[source]
> 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.)

79. troad ◴[] No.42161390{3}[source]
> It’s similar to how many complaints I’ve heard about Python because “it’s weakly typed”. What? Feel free not to like either of them, but make it for the right reasons, not because of a misunderstanding of how they work.

Are you sure you're not just being harsh to people whose grasp of CS vocab is weaker than yours? If someone tells me that Python is 'weakly typed', I translate it in my head to 'dynamically typed', and the rest of their complaint generally makes sense to me, in that the speaker presumably prefers static typing. Which is a valid opinion to hold, not necessarily the result of any misunderstanding.

replies(1): >>42161462 #
80. s17n ◴[] No.42161413[source]
If you aren't writing low level code, why not use a GC language?
replies(1): >>42162710 #
81. kstrauser ◴[] No.42161462{4}[source]
Reasonably sure. If it’s clear they actually mean dynamically typed, fine. That’s down to preference, and I won’t say they’re wrong any more than I’ll argue that chocolate is better than strawberry.

However, I’ve heard lots of utterly wrong criticisms of Python (and Rust and…) that were based on factual misunderstandings and not just a vocabulary mistake.

82. bargainbot3k ◴[] No.42161510[source]
Embedded. Your UB is my opportunity.
replies(2): >>42161955 #>>42184081 #
83. Olumde ◴[] No.42161511{3}[source]
Agreed. This is probably the reason why rewriting in Rust "works" -- because the architecture has been previously worked out.
84. liontwist ◴[] No.42161644[source]
Daily reminder that every Turing complete language has undefined behavior.
replies(1): >>42161952 #
85. bitbasher ◴[] No.42161656[source]
I 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.

86. chrismorgan ◴[] No.42161684[source]
> 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?

replies(2): >>42161899 #>>42162590 #
87. Sytten ◴[] No.42161860[source]
People 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.
88. ilaksh ◴[] No.42161879[source]
I 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.

replies(1): >>42162123 #
89. dafelst ◴[] No.42161886{4}[source]
The borrow checker isn't just about UB, it is mostly about memory safety.

I'm sure you've seen plenty of use-after-frees/use-after-move/dangling pointer type things or null pointer derefs, or data races, etc etc. These are largely impossible to do in safe rust.

90. wavemode ◴[] No.42161896[source]
This 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.

replies(2): >>42162053 #>>42162247 #
91. eviks ◴[] No.42161899[source]
You can do a good job by offloading that cognitive overhead to better tooling
replies(1): >>42162565 #
92. zanderwohl ◴[] No.42161933{3}[source]
> This is the #1 problem I see with people trying to learn a new language (not just Rust).

Definitely! I've also noticed people will learn a group of similar languages, like Java, C#/.Net, then Kotlin as the most distant relation. Now, they think they know many languages, but they mainly have the same core idea. So when they try something new like Haskell or Swift or Rust, they think it's doing something different from the "norm" in a really irritating way.

93. riwsky ◴[] No.42161950{3}[source]
And 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.
replies(1): >>42162062 #
94. emtel ◴[] No.42161952[source]
What? That’s not true. Turing machines themselves do not have undefined behavior.
replies(1): >>42162052 #
95. cyberax ◴[] No.42161955{3}[source]
Really? So far it seems like most of the UBs in C are caused either by:

1. Masochism

2. Underspecification, in a vain attempt to make a language that can theoretically be used on PDP computers.

replies(1): >>42162106 #
96. cyberax ◴[] No.42161962{4}[source]
> Any example except C++?

Java for sure.

> However, standard library collections throw “the collection was modified”

Java is similar, but the exception is done on a "best effort" basis, and is not guaranteed.

97. olvy0 ◴[] No.42162033[source]
This 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.
replies(1): >>42163193 #
98. ◴[] No.42162050[source]
99. liontwist ◴[] No.42162052{3}[source]
Halting 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.

replies(1): >>42162125 #
100. jandrewrogers ◴[] No.42162053[source]
I 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).

101. maxbond ◴[] No.42162062{4}[source]
I'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.

replies(1): >>42166259 #
102. loeg ◴[] No.42162100{4}[source]
I 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.

103. amluto ◴[] No.42162106{4}[source]
You’re missing #3, which accounts for an absolutely enormous amount of loss:

3. The fact that an inappropriate write through a pointer results in behavior that is so undefined that it can lead to remote code execution and hence do literally anything.

No amount of additional specification can fix #3, and masochism cannot explain it.

One could mitigate #3 to some extent with techniques like control flow integrity or running in a strongly sandboxed environment.

replies(3): >>42162379 #>>42162414 #>>42162592 #
104. jchw ◴[] No.42162123{3}[source]
In my opinion, it seems like you may be taking random internet discussions a bit too seriously; I don't actually expect too much meaningful programming language discourse to occur in Hacker News comments. I think the reason why I keep coming here is in part because it's one of the rare public forums where occasionally some truly interesting discussions really do happen, but don't forget Sturgeon's Law. For better or worse, public and open forums are rarely productive places to have discussions, and a lot of the real innovations certainly seem to happen behind closed doors. (Personally I greatly prefer public forums for discussion, and even would prefer anonymity if it were feasible, but I take what I can get.)

What does that say about participating here? Well, for me, sometimes when I write a comment that I feel is constructive, reasonable, and honest, it goes gray anyways, and it's easy to chalk it up to people just irrationally downvoting it because they don't like my opinion. It's also pretty easy to do this, I just need to be cynical about Apple or optimistic about the Go programming language, or something similar, and there's some percentage chance it will go negative depending on presumably who sees it first. It's not going to stop me from doing so, and ultimately it's pretty inconsequential, as I'm just some guy and my opinions are not really that important anyways.

Somehow, even though I have all of this internalized, I can't help but go 30 nested replies deep into threads debating about something senseless and unimportant, but it almost feels like it wouldn't be the Internet without debates like that. XKCD 386.

105. MathMonkeyMan ◴[] No.42162125{4}[source]
Undefined 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".

replies(1): >>42162196 #
106. nurettin ◴[] No.42162160{4}[source]
Python will raise a runtime exception if you modify the dict you are iterating over. You can work around that by copying a snapshot of the whole thing or just the keys and iterating over that.

C++ will laugh in invalidated iterators.

Of course you can erase from the container you are iterating over, but you have to make sure to continue the iteration using the iterator returned from the erase function and avoid storing copies of .end()

107. PittleyDunkin ◴[] No.42162166[source]
> I still think that unless you need the performance, you should use a higher level language which hides this from you.

Exporting and consuming the full c abi with very little effort is also another huge thing in rust's favor. Languages have opted heavily for supporting calling into the c abi and being hosted by the c abi, so naturally support for rust on the same terms comes for free. There's even rust in linux now.

108. liontwist ◴[] No.42162196{5}[source]
I 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.

replies(1): >>42162690 #
109. jltsiren ◴[] No.42162247[source]
That 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.

110. melodyogonna ◴[] No.42162250[source]
It'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.

111. jjnoakes ◴[] No.42162308{4}[source]
The number of times you shot yourself in the foot that you know about. Some of those bullets just haven't landed yet. C and C++ give you very interesting foot-guns: sometimes they go off even when you don't touch them (compiler upgrade, dependencies changing, building on a new architecture, ...)
112. Animats ◴[] No.42162317[source]
Rust'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

[2] https://internals.rust-lang.org/t/zero-cost-interior-mutabil...

[3] https://www.darpa.mil/program/translating-all-c-to-rust

replies(1): >>42165075 #
113. tsimionescu ◴[] No.42162360{4}[source]
In 30+ years of experience in C, you haven't used a free()d variable or written past the end of a buffer more than 5 times? If that's true, then you have more care and attention than 99.99% of all C experts.
replies(1): >>42164732 #
114. dbtc ◴[] No.42162370{3}[source]
> In C, you talk more to the computer with the language syntax, whereas in Rust you talk to the compiler.

C is like a fast motorcycle, rust is a car with driver-assistance system.

115. Dylan16807 ◴[] No.42162379{5}[source]
That's not missing, I think they left it out of the "most" criticism on purpose. A dangling pointer is one of the few really good cases for UB. (Though good arguments can be made to give the compiler less leeway in that situation.)
116. dgoldstein0 ◴[] No.42162386{5}[source]
Python defined the semantics of modifying lists while iterating over them, which is better than c/c++ just calling it undefined behavior, but I've basically never seen it not be a bug. Either I end up creating a copy of the list to iterate over, or figure out a way to defer the modifications, or write a new list. Imo the c# /Java behavior of detecting and throwing is probably the best option for non borrow checked languages.
117. tsimionescu ◴[] No.42162391{4}[source]
Not if you're iterating and have to make fundamental changes. Just like certain advanced type systems, encoding too much at compile time means you have to change a lot of code in unnecessary mechanical ways when the design constraints change, or when you discover them.

This is not a bad thing by the way, it's an extremely plausible design chocie, and is one that Rust made very clearly: rejecting not-entirely-correct programs is more important than running the parts that do work. Languages that want to optimize for prototyping will make the opposite choice, and that's fine too.

118. sfink ◴[] No.42162397[source]
Sounds familiar. I'm a relative beginner at Rust, but I've started feeling like I get the hang of the borrow checker rules and can get something close to right on my first writing. Finally. Ok, maybe 2nd or 3rd, but definitely before the dozenth!

Or at least I thought I did, until I launched into a project that mixes async and threading. That's where I hit a wall. What it's complaining about makes sense to me, but how to fix it does not -- partly because the async and threading come from libraries that I'm trying to stitch together. They necessarily have their own idiosyncratic ways of dealing with the issues, and as a beginner I don't even fully understand the problem they're solving let alone their solutions.

(For the record, I'm basically trying to force an unholy matrimony of matrix-sdk + tokio + pyo3 + pyo3-asyncio. You can take Python's GIL to grab out values to work with, but then if you want to do an async function then you'll have to release the GIL to stuff things into a future, and... well, if I fully understood the problem then I wouldn't be here whining about it, would I?)

119. tsimionescu ◴[] No.42162412{3}[source]
Unfortunately, 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.

replies(2): >>42162816 #>>42166078 #
120. thaumasiotes ◴[] No.42162414{5}[source]
> The fact that an inappropriate write through a pointer results in behavior that is so undefined that it can lead to remote code execution

This is a strange way to look at it. You'd get remote code execution only if the result of writing through the pointer was exactly what you'd expect: that the value you tried to write was copied into the memory indexed by the pointer.

121. sfink ◴[] No.42162416[source]
I'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.

replies(3): >>42164716 #>>42165098 #>>42165223 #
122. oneshtein ◴[] No.42162444{4}[source]
Borrow checker checks memory safety. Undefined/unspecified behavior still present in Rust[1].

[1]: https://doc.rust-lang.org/reference/behavior-considered-unde...

replies(1): >>42162632 #
123. thaumasiotes ◴[] No.42162457[source]
A 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.
124. o11c ◴[] No.42162462{3}[source]
And yet, such mutation is perfectly safe as long as you only change the value of existing keys. But you can't do that in Rust.
replies(1): >>42162514 #
125. blub ◴[] No.42162494[source]
I think you’re missing the author’s point, but OTOH he undermined it himself by stating that learning the rules helps: because Rust requires that the ownership and relationships are encoded in the type system, it requires significant design changes when those relationships change.

Learning the rules only partly mitigates this, because sometimes one does exploratory programming and isn’t sure what the final types are or they just want to change something.

Rust thrives on over-specification which calcifies the APIs.

Anyway, just as the author’s allegedly holding Rust wrong, one could say that you’re holding C++ wrong - the right approach is to learn how to write correct code and then the exceptions. Also accept and be at peace with the fact that your code will have some bugs. I don’t know why the average Rust developer is so obsessed with getting things perfect and no less with memory safety when the overall software quality is the way it is. I mean if someone’s researching the topic or works on Rust, sure, be the Stallman of memory correctness.

replies(1): >>42164159 #
126. oneshtein ◴[] No.42162509[source]
> Rust can be hard to get right because of the borrow checker.

In the same vein: «C/C++ can be hard to get right because of the valgrind.» ;-)

127. dwattttt ◴[] No.42162514{4}[source]
You would use interior mutability, putting things in Cells in the map.
128. blub ◴[] No.42162516{3}[source]
This only makes sense if one wants to write a Phd on C++ UB and needs the exhaustive list.

For the rest of us, there’s cppreference, UBsan and quite a few books on writing correct C++ code. Of course, these will still not suffice to write 100% memory safe code, which is a pretty arbitrary goal that just happens to match what Rust offers and is pushed a lot by Rust advocates.

It’s a nice goal, but not everybody works on software that’s attacked all day every day.

replies(1): >>42163162 #
129. scott_w ◴[] No.42162555[source]
After reading the article, it’s clear the author approves of the fact Rust has these rules (and prefers it over C++). They’re highlighting the natural challenges that brings so future iterations or competitors can see what needs to be improved.
130. chipdart ◴[] No.42162556[source]
From 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?

replies(1): >>42162597 #
131. bombela ◴[] No.42162565{3}[source]
What if Rust itself is the better tooling?
replies(1): >>42163509 #
132. blub ◴[] No.42162570[source]
What did the Rust community expect to happen if they so strongly and often claimed that Rust is easy to learn and user-friendly?

And how did we get from that to “for some people, some languages may never fully agree with their preferred style of writing code”?

If a C++ programmer (ideal Rust learner) says four years in that the ergonomics of Rust are bad, then based on my own experience I will believe them.

I don’t write something to see if it compiles either, I design every single line of code. But with Rust (and a lesser extent C++) a lot of that design is for memory safety and not necessary for the algorithms.

replies(3): >>42162676 #>>42163188 #>>42163655 #
133. chombier ◴[] No.42162590[source]
> Of course you have to internalise the rules of a borrow checker

This is generally a good thing: the more you internalise the logic of borrow checking, the earlier you start thinking about "who owns what" instead of deferring the choice to later, which often ends up in a tangled mess of "incidental data structures" as it is sometimes called in the c++ world [1].

Of course in c++ this means you have to internalise this discipline the hard way, i.e. without the borrow checker helping you.

[1] https://isocpp.org/blog/2016/05/cppcon-2015-better-code-data...

134. cyberax ◴[] No.42162592{5}[source]
There's nothing really you can do with out-of-bounds write in C except say that it can do "anything". This UB is unavoidable.

I'm talking more about the nonsense like "c++ + ++c". There's no reason but masochism to keep it undefined. Just pick one unambiguous option and codify it.

An example of #2 is stuff like signed overflow. There are only so many ways to handle it: wraparound, saturate, error out. So C should just document them and provide a way to detect which behavior is active (like it does with endianness).

replies(1): >>42164505 #
135. ordu ◴[] No.42162593[source]
> whenever someone raises concerns about any element of Rust, no matter how constructively,

I don't think that it is a very constructive article. The author's critique of Rust raises questions like "how to do it better" but there are not answers.

> they're always met with a wall of "you must not truly get the borrow checker,"

Yeah, it is frustrating in this case particularly. The author openly states that he doesn't want to learn all the quirks of the borrow checker, and people respond to it with "you just don't get the borrow checker". I can see how this answer could be helpful, but if it was expanded constructively, if there was an explanation how it can become easy to deal with problems the author faces if you understood the borrow checker. OTOH I cannot see how such an argument can be constructed without a real example with the real code and the history of failed changes to it.

I personally feel, that the borrow checker is simple, if you got it. And the author's struggles just go away, if you got it. You can easily predict what will happen if you try this or that changes to the code, and you know how to do something so the borrow checker will be happy. But I cannot elaborate and to make it clear how it works.

136. throw_a_grenade ◴[] No.42162597[source]
The 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.

replies(1): >>42162730 #
137. chombier ◴[] No.42162609[source]
Besides, if you still want to skip learning there are escape hatches like Rc<RefCell> but these hint pretty strongly (e.g. clones everywhere) that something might be wrong somewhere.
138. donatj ◴[] No.42162613{3}[source]
Trying to convince developers from "classical" OO that not everything needs to be a class in JavaScript has been a major thorn in my side for years. Your little procedure with no state? That can just be an exported function.
139. chipdart ◴[] No.42162621[source]
> I have memorised the UB rules for C.

Why? What's wrong with using one of the many static code analysis tool to tell you about them if/when they appear?

replies(1): >>42163225 #
140. bombela ◴[] No.42162632{5}[source]
Only from code annotated unsafe. In other words, if you do not use the keyword unsafe, you have no undefined behaviors.
141. chipdart ◴[] No.42162641[source]
> I think it's telling that whenever someone raises concerns about any element of Rust, no matter how constructively (...)

I'm far from a Rust expert, but to me if someone is whining about how it is hard to track lifecycle rules of an object because they are passing it through long chains of function calls across all sorts of boundaries, what this tells me is that you're creating your own problems that you could avoid if you simply passed the object by value instead of by reference. I mean, if tracking life cycles is a problem then why not prevent it from being a problem? Not all code lives in the hot path. I'm sure your performance benchmarks can spare a copy somewhere.

replies(1): >>42163643 #
142. rapsey ◴[] No.42162676{3}[source]
Rust is easy to learn and user friendly. But if you are stuck in your ways and insist on writing code exactly like you did in another language you will have a hard time. This is true for every language on earth. Rust will refuse to compile, whereas other languages give you much more freedom to not use them as they are meant to.

> If a C++ programmer (ideal Rust learner)

There is no ideal learner.

replies(3): >>42162698 #>>42163187 #>>42163563 #
143. chipdart ◴[] No.42162683{3}[source]
> This just seems to be the standard excuse I read any time someone has a critique of Rust.

The guys who parrot this blend of comments don't seem to be aware that cognitive load is a major problem, not a badge of honor.

replies(1): >>42164589 #
144. Dylan16807 ◴[] No.42162690{6}[source]
> I understand and my point is rust has no “ub” only because there is no spec, not because it avoids inherent computing problems.

Well, your point is wrong because UB is not an inherent computing problem. That's what the post above tried to explain.

Many forms of UB are inherent to C-like languages, but languages don't have to be C-like.

> For example infinite template recursion is undefined. Specifying any other behavior is impossible due to halting problem.

A language can avoid this by not having infinite template recursion.

C++ currently allows infinite recursion at the language level, while acknowledging that compilers might abort early and recommending that 'early' is a depth of 1024 or higher. But a future version could bake that limit into the language itself, removing the problem.

> Another example: a system might be able to detect out of bounds pointer deref, or maybe not. Same with signed integer overflow.

A language can avoid out of bounds deref in many ways, one of which is not allowing pointer arithmetic.

Signed integer overflow is trivial to handle. I'm not sure what problem you're even suggesting here that the person in charge of the language spec can't overcome. C++'s lack of desire to remove that form of UB is not because it would be difficult.

replies(1): >>42165211 #
145. chipdart ◴[] No.42162698{4}[source]
> Rust is easy to learn and user friendly. But if you are stuck in your ways (...)

It's high time that people in the Rust community such as you just stop with this act.

The Rust community itself already answered that they find "Rust too difficult to learn or learning will take too much time" as a concern to not use Rust. The community also flagged Rust being too difficult as one of the main reasons they stopped using Rust.

https://blog.rust-lang.org/2024/02/19/2023-Rust-Annual-Surve...

replies(1): >>42162785 #
146. dathinab ◴[] No.42162699[source]
I 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)

147. FridgeSeal ◴[] No.42162710{3}[source]
Great 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.

replies(1): >>42162884 #
148. dathinab ◴[] No.42162713[source]
> zero evidence

you mean except the original poster often implying exactly that in their articles or the personal experience of the commenter that nearly always whenever they ran into borrow checker problems it was due to exactly that reason

> There's never anything to improve on Rust, it's always user error / a skill issue.

RFCs get accepted and implemented nearly every week, like in any language there is always a lot to improve

the problem is that the complains of such articles are often less about aspects where you can improve things but more about "the borrow checker is bad" on a level of detail which if you consider the borrow checker a fundamental component basically is "rust is fundamentally bad"

replies(1): >>42163005 #
149. chipdart ◴[] No.42162730{3}[source]
> 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.

replies(1): >>42162899 #
150. rapsey ◴[] No.42162785{5}[source]
Anyone who has managed to become proficient in C++ is smart enough to be proficient in Rust. The only too difficult part is adapting to the rust way of doing things and some refuse to.
replies(4): >>42163023 #>>42163195 #>>42163635 #>>42166726 #
151. ◴[] No.42162816{4}[source]
152. neonsunset ◴[] No.42162884{4}[source]
F# :)

Expression-oriented, HM type inference with gradual typing, faster than other FP languages, can even reach for low-level bits, or write extra glue code in C# which is more pleasant at low-level imperative code.

Not sure what exactly you refer to as idiomatic C#. If it has too much ceremony chances are it’s anything but!

replies(1): >>42163451 #
153. throw_a_grenade ◴[] No.42162899{4}[source]
TFA argues developers add it left and right, unless someone memorised all the rules. Since reportedly it's not possible to memorise all the rules by a single human, then you either "simply add undefined behaviour", or limit yourself to a subset of C++ that programmers do actually understand. Which is a solution I see in many codebases: to limit a set of permissible constructs by a style manual.
154. ramon156 ◴[] No.42163005{3}[source]
Yes, and that's why the comments usually are in some sense "you don't use the BC right", because they don't want a BC. And that's fine, but you can't blame Rust for this
155. throwaway81523 ◴[] No.42163023{6}[source]
There is a saying among Haskellers that "Haskell has the steepest unlearning curve". Sounds like Rust is similar.
156. goku12 ◴[] No.42163093[source]
People conflate difficulty of Rust borrow checker with the inherent difficulty in systems programming. However, this is not true. Both C and C++ are primarily systems programming languages. However, that never dissuaded anyone from using them as general purpose programming languages. The same should apply to Rust as well.

Meanwhile, Rust is my programming language and I choose it unless there is a good reason to choose another language. I never really struggled with the borrow checker. I think a lot of beginners approach the BC wrongly. Trying to memorize the rule is definitely the wrong way.

157. kimixa ◴[] No.42163162{4}[source]
Also, memory safety isn't the only "bug" - I'd even argue that the majority of "memory" issues in unsafe languages like C are actually the result of a logic error or mismatch of interface expectations, and a memory error is often the "first noticed failure". In the trivial example strcpy() examples people love to use, unexpectedly truncating a string often means the program has "failed" in it's intended task just as much as a segfault or other memory corruption.

I'm extremely positive on highlighting as many of these problems before it gets to the user's hands, even more so if it's as early as a compile step as in the borrow checker, but lets not delude ourselves that they are the only possible issue software has. Or that in many languages it's a tooling issue (or culture issue accepting that tooling...) rather than a fundamental language difference.

On a side node, with the prevalence of things WASM I feel some people are just redefining what "memory safety" is. Defining a block of memory and using offsets within that is just reinventing pointers, the runtime ensuring that any offsets are within that block just mirroring the MMU and process isolation. We should really be looking at why that isn't well used rather than just reimplementing a new version on top for "security", as if those reasons aren't really "technical" (IE poor isolation between "Trusted" and "Untrusted" data processing in separate processes due to it being "Easier") we need to ensure we don't just do the same things again, and if they are technical we can fix them.

158. pansa2 ◴[] No.42163170{3}[source]
> The problems come when they try to learn a new language without giving up the old ways

In Python, I frequently see the same problem from the other side. Instead of C/C++ programmers learning Rust and "not wanting to learn the rules of Rust", it's Java/C# programmers learning Python and not wanting to unlearn the rules of Java/C#. They write three times as much code as they need to - introducing full class hierarchies where a few duck-typed functions would do.

159. 7bit ◴[] No.42163187{4}[source]
Rust is not easy to learn. Stop saying that. It is hard. It is harder than C#, harder than Python, harder than Java, harder than PHP, harder than JS, Harder than TS.

Saying it is easy to learn is just delusional and does a disservice to the language. Rust has many advantages, but trying to get people learning the language by lying about why they should learn it is just dumb.

160. Ygg2 ◴[] No.42163188{3}[source]
> What did the Rust community expect to happen if they so strongly and often claimed that Rust is easy to learn and user-friendly?

No one really claimed it was easy to learn. Find me one Rustacean that claims Rust is as easy as Python/Ruby/Go.

But it is higher level language with decent ergonomic and in such way it can be interpreted as user-friendly.

> If a C++ programmer (ideal Rust learner)

If Java programmer (me) can learn Rust in a year enough to contribute to Servo development, you (ideal Rust learner) should have no problem either.

Plus Google saw about 6-month to get people up to speed with Rust.

replies(1): >>42164183 #
161. goku12 ◴[] No.42163193[source]
Rust 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.

162. 7bit ◴[] No.42163195{6}[source]
Everybody who has managed to become proficient building a school is smart enough to be proficient in building a library.

So building libraries must be easy, that's totally why you can become an architect over night ... What a stupid argument you delivered

163. akkad33 ◴[] No.42163210{4}[source]
There are improvements to the borrow checker that is in the roadmap for Rust 2024. So it's not like the "community" is claiming there are no issues
164. rcxdude ◴[] No.42163225{3}[source]
Those tools can't reliably identify undefined behaviour.
replies(1): >>42163678 #
165. Ygg2 ◴[] No.42163261{3}[source]
That's one of their issues. Other issues are borrow virality and stuff for next Rust.
166. goku12 ◴[] No.42163269[source]
That 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.

replies(1): >>42163551 #
167. FridgeSeal ◴[] No.42163451{5}[source]
Yeah I’ve used F# before! It was pretty good, some solid features and nice experience. It just falls into a bit of weird place IMO? You have to rely on writing/using C# to fill any holes, and I really dislike that language/ecosystem, and why split between 2 lands when I can just get the same HM type system, similar-enough principles, better perf and no MS taint.

Edit: I do love the ML style syntax though, Haskell, F#, Dhall are awesome, I wish it were more readily accepted.

replies(1): >>42164656 #
168. audunw ◴[] No.42163509{4}[source]
Is 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.

replies(1): >>42164950 #
169. James_K ◴[] No.42163551{3}[source]
Manually 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.
replies(1): >>42163656 #
170. Arch-TK ◴[] No.42163562{3}[source]
That's the appendix containing documented UB. The standard also explicitly states that any behaviour not explicitly defined by the standard is undefined meaning that there are things which aren't in that list. And I can confirm, there are things which you can do in C which are UB but which are not on that list.
171. brabel ◴[] No.42163563{4}[source]
You just seem to have missed the point the author was making. I will try to clarify it: when you find out that a decision you made early on didn't pan out and you're forced to change the lifetime of some data, this will incur major refactoring in Rust and that will cause you to lose a lot of time. It's nearly impossible to avoid mistakes like this, not because you don't know Rust enough, that's almost completely irrelevant... it's because you just can't predict the direction your design will go to after many iterations and changes in requirements, which are unavoidable in the real world.

Replying to that with "Rust is easy to learn" just makes it sound like you didn't even understand what you're trying to reply to.

172. goku12 ◴[] No.42163599[source]
If 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/

[2] https://cheats.rs/#basic-types

[3] https://rust-unofficial.github.io/too-many-lists/

173. chipdart ◴[] No.42163635{6}[source]
> Anyone who has managed to become proficient in C++ is smart enough to be proficient in Rust.

I'm not sure you're paying attention. The people who are saying Rust is too hard are the Rust community itself. They said so in Rusty's annual survey. The ones who participate in it are Rust users who feel strongly about the topic to participate in their governance.

It's Rust users who say Rust is too hard. There is no way around this fact.

174. brabel ◴[] No.42163643{3}[source]
> I mean, if tracking life cycles is a problem then why not prevent it from being a problem?

So you're suggesting that people should just wrap everything in Arc or make copies everywhere to avoid lifetimes? At that point why not just use Java/OCaml/Swift/your-favourite-GC-lang?

replies(1): >>42164417 #
175. tialaramex ◴[] No.42163655{3}[source]
> C++ programmer (ideal Rust learner)

Well there's your problem. Rust does look like a semi-colon language, that's intentional, but if that's all you understand you're probably going to struggle.

The "ideal Rust learner" would have an ML, such as Ocaml or F#, maybe some Lisp or a Scheme something like that, as well as a semi-colon language like C or Java not just the weird forced perspective from C++

One experiment I probably won't get to try is to teach Rust as First Language for computer science students. Some top universities (including Oxbridge) teach an ML as first language, but neither teaches Rust because of course Rust is (relatively) new. The idea is, if you teach Rust as first language your students don't have to un-learn things from other languages. You can teach the move assignment semantic not as a weird special case but as it it really is in Rust - the obvious default assignment semantic. I pass a Goose to a function, obviously the Goose is gone, I gave it to the function. Nobody is surprised that when they pass a joint they don't have the joint any more, so why are we surprised when we pass a String to a function and the String is gone now? And once we've seen this, the motivation for borrows (reference types) is obvious, often we don't want to give you the string, we just want to tell you about a string that's still ours. And so on.

replies(1): >>42164174 #
176. goku12 ◴[] No.42163656{4}[source]
Your 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).

replies(1): >>42163967 #
177. chipdart ◴[] No.42163678{4}[source]
> Those tools can't reliably identify undefined behaviour.

I'm sorry, can you explain what leads you to believe your hypothetical scenario is an argument rejecting the use of static code analysis tools?

I mean, I'm stating the fact that there are many many tools out there that can pick up these problems. This is a known fact. You're saying that hypothetically perhaps they might not catch each and every single hypothetical case. So what?

replies(1): >>42164203 #
178. James_K ◴[] No.42163967{5}[source]
You seem to have forgotten that garbage collected languages exist, and are much preferable to Rust in many circumstances.
replies(1): >>42165827 #
179. ◴[] No.42164055[source]
180. Arch-TK ◴[] No.42164159{3}[source]
I think unless your code is guaranteed to never interact with any untrusted input it is nowadays an increasingly unacceptable compromise to just accept that your program might have serious flaws which can lead to remote code execution or worse.

Moreover, it becomes increasingly unpleasant and unworkable to deal with code which progressively gets more and more unreliable.

It's expected that if the complexity of a program grows, the state space that the program can occupy grows with it. But with UB you can run into by accident that state space seems to grow exponentially in comparison to a language like Rust.

If you are required to write code at that low level, I would not use anything other than something like rust.

If you are not required to write code at that level. There are many languages with much less uncertainty than C++ which are much more productive than either C++ or rust.

replies(1): >>42166957 #
181. chipdart ◴[] No.42164174{4}[source]
> Well there's your problem. Rust does look like a semi-colon language, that's intentional, but if that's all you understand you're probably going to struggle.

This is a silly point to make. What makes a C++ programmer a C++ programmer is not an uncanny ability to find a semicolon on the keyboard. It's stuff like using low-level constructs, having a working mental model of how to manage resources at a low level down and how to pass and track ownership of resources across boundaries. This is not a syntax issue.

It's absurd. You have people claiming that Rust is the natural progression for C++ programmers because their skillsets, mental models, and application domain overlap, but here are you negating all that and try to portray it as a semicolon issue?

replies(2): >>42165345 #>>42166672 #
182. chipdart ◴[] No.42164183{4}[source]
> No one really claimed it was easy to learn.

It's a great example of unintended comedy the fact that the comment right below yours is literally "Rust is easy to learn and user friendly."

https://news.ycombinator.com/item?id=42162676

replies(1): >>42165805 #
183. rcxdude ◴[] No.42164203{5}[source]
They're a good idea, but not a substitute for knowing the rules. And they don't just miss theoretical cases, they miss problems in practice even when used rigourously.
replies(1): >>42164441 #
184. chipdart ◴[] No.42164417{4}[source]
> So you're suggesting that people should just wrap everything in Arc (...)

You're the only one who managed to come up with this nonsense. No one else did, and clearly you did not pick that from what I wrote because I definitely did no wrote that.

Please refrain from slippery slope fallacies.

185. chipdart ◴[] No.42164441{6}[source]
> They're a good idea, but not a substitute for knowing the rules.

It's a good thing no one made that claim, then.

The whole point is that were seeing people in this thread making all sort of wild claims on how it's virtually impossible to catch these errors in C++ even though back in reality there are a myriad of static analysis and memory checker tools that do just that.

Your average developer also knows how to type in a space character but still it's a good idea to onboard linters and automatic code formatters.

replies(2): >>42164644 #>>42165992 #
186. jcranmer ◴[] No.42164505{6}[source]
It's someone disingenuous to purposefully ignore what is the most common kind of UB in C. It's also ultimately not a very useful dichotomy, especially because it misunderstands why behavior ends up being undefined. For example:

> I'm talking more about the nonsense like "c++ + ++c". There's no reason but masochism to keep it undefined. Just pick one unambiguous option and codify it.

It's because there's an underlying variance in what the compilers (and the hardware [1]) translated for expressions like that, and codifying any option would have broken several of them, which was anathema in the days of ANSI C standardization. (It's still pretty frowned upon, but "get one person to change behavior so that everybody gets a consistent standard" is something the committees are more willing to countenance nowadays).

> An example of #2 is stuff like signed overflow. There are only so many ways to handle it: wraparound, saturate, error out.

Funnily enough, none of the ways you mention turn out to be the way it's actually implemented in the compiler nowadays.

As for why UB actually exists, there are several reasons. Sometimes, it's essential because the underlying behavior is impossible to rationally specify (e.g., errant pointer dereferences, traps). Sometimes, it's because you have optimization hints where you don't want to constrain violation of those hints (e.g., restrict, noreturn). Sometimes, it's erroneous behavior that's hard to consistently diagnose (e.g., signed overflow). Sometimes, it's for explicit implementation-defined behavior, but for various reasons, the standard authors didn't think it could be implemented as unspecified or implementation-defined behavior.

[1] Remember, this is the days of CISC, and not the x86 only-very-barely-not-RISC kind of CISC, the heady days of CISC where things like "*p++ = --q" is a single instruction.

187. Const-me ◴[] No.42164582[source]
> 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 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.

188. eddd-ddde ◴[] No.42164589{4}[source]
Learning the language fundamentals is not "cognitive load" more so for rust than c.

With c you also need to understand pointers, manual allocation, volatility, is that bad?

189. eddd-ddde ◴[] No.42164644{7}[source]
You made the claim

> Why? What's wrong with using one of the many static code analysis tool to tell you about them if/when they appear?

You clearly pose static analysers as an alternative to understanding UB. You still need to understand how things work.

190. neonsunset ◴[] No.42164656{6}[source]
The hatred of .NET (and C#) is unfortunate, irrational and unwarranted. I ended up unfortunately resorting to just thinking less of engineers that have it, because they can’t update their priors (“it was slightly inconvenient 8 years ago so it must be bad today surely”) and distinguish between Microsoft’s other products and policies and .NET itself.
replies(1): >>42165130 #
191. eddd-ddde ◴[] No.42164671{4}[source]
Clearly you must be superhuman then, something as simple as forgetting a null pointer check is bound to hit you every now and then.
replies(1): >>42164757 #
192. wduquette ◴[] No.42164689[source]
This 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). 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.

193. eddd-ddde ◴[] No.42164716[source]
> 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.

replies(1): >>42165039 #
194. kanbankaren ◴[] No.42164732{5}[source]
I should have been clear.

Of course, I have done such mistakes, but they were caught early in the dev. process. I am talking about bugs that were caught in production due to misunderstanding of C compilers on 16/32 bit processors.

I also avoid idioms like *p instead write p[i] whereever possible.

195. kanbankaren ◴[] No.42164757{5}[source]
Of course, I do, but they are caught early in the dev. process. Not in production though.
replies(1): >>42165006 #
196. bombela ◴[] No.42164950{5}[source]
My short comment was a bit tongue in cheek.

> 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.

That's exactly what the Rust borrow checker does for me.

The borrow checker rules are quite simple conceptually.

If I own a book, I can read it, write in the margins or even destroy it. `let book: Book = Book{...}`.

I can lend this book to you exclusively `&mut Book`, you can read and write it, but not destroy it. And nobody else; including me; can even read it until you are done.

I can lend this book to you and others for reading `&Book`. We can all read it concurrently. And I must wait for everybody to be done before I can regain full ownership.

I can give you the book (passing by value). And it's now yours to do what you please. Including destroying it `drop(Book)`.

Sometimes you do want to share to many; and maybe even gate exclusive write access; at runtime. This is where Rc, Arc, Cell, RefCell and Mutex come in.

Rc and Arc destroy the book when a reference counter drops to zero. Another way to look at it, is that when the counter is 1, you have sole ownership of the book. And you can do with it what you please.

As for the runtime check for mutability, Mutex should be obvious. Cell and RefCell are similar but within a single threaded context.

And finally when you know better than the compiler, you use pointers (instead of references) and triple check your work within `unsafe` blocks.

197. kstrauser ◴[] No.42165006{6}[source]
I would contend that’s an unusually sophisticated dev process not used by most.
198. ufmace ◴[] No.42165016{3}[source]
I 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.
replies(1): >>42166532 #
199. kstrauser ◴[] No.42165039{3}[source]
Agreed. 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.
200. oconnor663 ◴[] No.42165075[source]
I 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. 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.)
replies(1): >>42166098 #
201. oconnor663 ◴[] No.42165098[source]
> 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?

There's some truth to that, but I think the real problem is when some part of the state of your program looks like a graph with loops and cycles (not a tree). It's possible that it only looks that way during prototyping, but I think it's more likely that once it starts looking that way, it's gonna stay that way. In that case, "hiding" your lifetimes from the borrow checker is really about making your borrows shorter, which is how you can manage a graph without violating the no-mutable-aliasing rule.

https://jacko.io/object_soup.html

202. kstrauser ◴[] No.42165130{7}[source]
I believe that’s shortsighted. I’ve been adjacent to any number of Microsoft rugpulls over the years (Visual FoxPro.NET any day now, promise!). Their are enough non-MS languages that C#’s never tempted me. I don’t think it’s a bad language and don’t think poorly of you for using it, but I feel much better about the long-term prospects of Rust and wouldn’t likely build a new business on C# today.
replies(1): >>42165709 #
203. IshKebab ◴[] No.42165146{4}[source]
Most Rust code is not purely functional in my experience. It's quite similar in style to C++ except:

1. No class hierarchies and inheritance.

2. The borrow checker forces a tree structured ownership style. You don't get spaghetti ownership. This is generally great because that coding style leads to fewer bugs. But sometimes it is annoying and you have to use indices rather than pointers as references.

204. liontwist ◴[] No.42165211{7}[source]
> A language can avoid this by not having infinite template recursion.

How does it know whether a definition is infinitely recursive? This IS the halting problem.

> But a future version could bake that limit into the language itself,

In other words, take away the Turing completeness of templates. Which goes back to my original comment.

Also note that limiting recursion hurts real world use case (types getting arbitrarily complex in a program over time) in favor of theoretical benefit (now you can say it’s not UB).

> pointer Deref

Let me explain again. In a language with pointers checking whether a deref is valid requires comparing every address to every allocation bounds. That’s ridiculously expensive.

The only solution is to take away pointers (Java, C#, etc) OR do what C does, crash on obviously bad derefs. Since “obviously bad” depends on the implementation (maybe you are a safety sadist and you want 2000 instructions per deref) the standard cannot guarantee any behavior. Maybe it crashes, maybe you get “lucky” and it doesn’t notice.

The only way to avoid UB is to limit expressiveness (pretend addresses don’t exist), and all Turing complete languages have UB.

I have more responses, but you’re not grasping the ones I already made.

replies(1): >>42165675 #
205. IshKebab ◴[] No.42165223[source]
I don't think this is really unique to Rust. Any language that requires you to write stuff down about interfaces might require you to change them in a lot of places if you get it wrong. E.g. if you get the type of a parameter that is passed everywhere wrong.

I guess the hard bit here is it's more difficult to make a mental model of the borrow checker than it is most other features, and also the fact that borrow checking is a late phase of the compiler so you might put in a fair amount of work before getting feedback.

replies(1): >>42165604 #
206. tialaramex ◴[] No.42165345{5}[source]
Oh! Alas the apparent syntax similarity is misleading. If you prefer imagine I wrote "C-like language" anywhere that I wrote "semi-colon language". It's not really about the semi-colon, indeed the semi-colon isn't even doing the same thing and that's a clue†. You see, the typical semi-colon languages you'll have used such as C++ or Java have these "everything is a machine integer" type systems which were a small step up from the "Who needs a type system" of their predecessors but aren't anywhere close to what you'd expect in an ML or a functional language.

So this means there's a substantial learning curve, or, if you try to just keep writing C++ even though you're in Rust, an impedance mismatch. It looks superficially like the thing you're used to, but that's not what it is.

† In Rust the semi-colon is turning your expression into a statement, but in the semi-colon languages it's a separator, everything is a statement anyway. So while a rust program might say let x = if k > 3 { plenty(k) } else { too_few() }; in the semi-colon languages there's a whole separate ternary operator provided to do this trick - in fact they don't have any other ternary operators and so they often call this "the" ternary operator which is very funny if you come from a language which has the multiply-accumulate operator...

207. sfink ◴[] No.42165604{3}[source]
> I don't think this is really unique to Rust. Any language that requires you to write stuff down about interfaces might require you to change them in a lot of places if you get it wrong. E.g. if you get the type of a parameter that is passed everywhere wrong.

My experience doesn't match this. I think the difference is that Rust has... "specific types", to pick an arbitrary term. The types store more information, which means that there are many more ways that you might need to update an interface.

An interface with a function that takes a C++ pointer doesn't imply anything other than that the pointer is valid at the beginning of the calthe return value of l to that function. It doesn't even tell you whether it can be nullptr or not. It might be a pointer extracted from a `std::unique_ptr`, in which case the actual expectation is that the pointer be valid when the function returns. Or it might be passed the return value of `new`, in which case the expectation is that the pointer be either valid but now owned by something else, or invalid when the function returns. And you might change from one expectation to the other merely by removing a `delete` call 7 levels deep, without needing to adjust any of the intervening layers.

Rust, on the other hand, encodes a specific subset of the possible lifetimes that the C++ version accepts, and every intervening layer has to agree on that lifetime or at least the structure of it.

And it's not just lifetimes. In Rust, you'd typically make everything a specific type -- maybe an enum or Option or whatever. In C++, I often find myself intentionally degenerating even (C++) enums to ints when I need to store or output or manipulate them -- I want to test `val < FirstCustomValue` or something. A parameter that started out as an enum with 3 variants can add on a couple of orthogonal bits without perturbing much of anything.

The more specific the types used in practice, the more they'll need to be adjusted to accommodate changes. C++ doesn't even have the facilities for specializing the types as precisely, but more importantly types are typically left pretty loose in practice -- at great cost to stability and correctness, but with resulting benefit to adaptability.

A lump of mud is much more adaptable than a carved sculpture.

replies(1): >>42165948 #
208. Dylan16807 ◴[] No.42165675{8}[source]
> In other words, take away the Turing completeness of templates. Which goes back to my original comment.

Template halting is only a correctness issue because it's done at compile time. Turing completeness is not a problem in general, and limiting the amount of computation at compile time is fine.

> The only solution is to take away pointers (Java, C#, etc) OR do what C does

Something along those lines.

> The only way to avoid UB is to limit expressiveness (pretend addresses don’t exist), and all Turing complete languages have UB.

The only way to avoid UB is to limit expressiveness, and NOT all Turing complete languages have UB.

Pointers are a big thing to restrict for a safe language. But you really don't have to do that much else. Whether a program halts or doesn't halt at runtime isn't a safety issue, there's no UB involved. It just runs indefinitely.

209. neonsunset ◴[] No.42165709{8}[source]
It's not. It is shortsighted to blindingly hate thing X over Y instead of considering circumstances and understanding how big corporations work, or understanding the nature of the project you make use of (after all, the entirety of .NET is MIT) and just how much worse most alternatives are. There is a wealth of mainstream languages that are decently usable, which I wouldn't touch with a ten-foot pole as a main choice still because they carry a significant downgrade in one or another area, that .NET does not compromise on.
replies(1): >>42169898 #
210. goku12 ◴[] No.42165742{5}[source]
Shouldn't this be possible already? Deref is implemented for Arc<T>.

[1] https://doc.rust-lang.org/std/sync/struct.Arc.html#impl-Dere...

[2] https://rust-lang.github.io/rfcs/2005-match-ergonomics.html

replies(1): >>42166761 #
211. Ygg2 ◴[] No.42165805{5}[source]
Sure but that's a no one. Same as me. Find someone on Rust team saying that. Or someone teaching Rust constantly.

I can see user friendly. It's got a solid ecosystem around it, and compiler is super helpful with errors.

But easy to learn it ain't.

212. goku12 ◴[] No.42165827{6}[source]
You're diverting from the context of this thread. I'm asking why a developer who is comfortable with Rust borrow checker choose any other language? How is a GC language preferable in any way to Rust for such a person?

In a broader sense, I keep seeing some people asking everyone else to avoid Rust based on an exaggerated account of the struggle with the borrow checker. There is actually a way to get comfortable with the BC. Perhaps beginners should be introduced to those ideas rather than such negative takes.

replies(1): >>42167954 #
213. IshKebab ◴[] No.42165948{4}[source]
It sounds like you're agreeing with my point to be honest. The problem isn't Rust or the borrow checker; it's simply checking lots of things on interfaces. The more you check, the more you'll have to change when you get it wrong.

Rust happens to check quite a lot. I imagine it is similar for things like monads in Haskell, or probably formal languages like Lean and Dafny... but I have no experience of that so it's just a guess.

replies(1): >>42166574 #
214. rcxdude ◴[] No.42165992{7}[source]
It's not impossible to catch those errors in C and C++. In fact, every time you run a new tool against a large C or C++ codebase you will find new ones. What none of these tools do is catch all the issues, as demonstrated by the fact that people keep finding new ones.
215. hypeatei ◴[] No.42166078{4}[source]
The 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

replies(1): >>42173198 #
216. Animats ◴[] No.42166098{3}[source]
> 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.

replies(1): >>42169429 #
217. riwsky ◴[] No.42166259{5}[source]
The 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.

replies(1): >>42167214 #
218. Const-me ◴[] No.42166532{4}[source]
> 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 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.

replies(1): >>42167263 #
219. sfink ◴[] No.42166574{5}[source]
No, I'm not agreeing with you. You're agreeing with me.

Wait a minute... :-)

Sorry, somehow I managed to quote the portion of your message that I was agreeing with. You just said it more concisely than I could.

The aspect I disagree with is whether brittleness to change is more due to the complexity of the borrow checker vs interfaces with very constrained types. I mean, the complexity of the borrow checker definitely factors into it, especially with how late it runs (as you said) and the fact that it's all or nothing, which makes it hard to change things incrementally.

But (imho) the borrow checker is less complex than, say, the rules around C++ constructor variants. And RVO/NRVO. And name lookup. And definitely less complex than undefined behavior. I'd rather memorize the borrow checker than those. They contribute to some amount of brittleness to change, especially when monkeying with move constructors, but in practice it seems far less than Rust. I really think it's more about the specificity of types in the language.

I should probably mention that I still think it's a good tradeoff for many of the types of programs I write, but it is a tradeoff.

Anyway, this is all more just a realization that you sparked in me. It's ok if we, uh, disagree to agree.

220. sfink ◴[] No.42166672{5}[source]
That's an... uncharitable interpretation of the phrase "semi-colon language".

The comment made sense to me: it's not at all about semicolons, but the semicolon isn't a bad marker to distinguish between language families. It's closely related to expression- vs statement-focused languages, which if you squint is similar to the distinction between imperative and functional languages. Though the latter distinction is less relevant here, I'm just using it to point out that semicolon syntax is suggestive of semantics, even if it is itself just a superficial bit of syntax.

You are correctly describing the awareness of resource management as an important characteristic, but that resource management is heavily intertwined with how values flow through the language syntax, which brings us back to expressions vs statements.

221. sfink ◴[] No.42166726{6}[source]
> Anyone who has managed to become proficient in C++ is smart enough to be proficient in Rust.

I agree completely. But I'd reword your statement slightly as "Anyone who has managed to become proficient in the language that is notorious for being one of the most complicated and convoluted languages in existence, with the most footguns per line of code and a CVE record that proves its difficulty, ..."

It changes the conclusion just a tad.

222. estebank ◴[] No.42166761{6}[source]
I 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.
replies(1): >>42167408 #
223. sfink ◴[] No.42166957{4}[source]
> I think unless your code is guaranteed to never interact with any untrusted input it is nowadays an increasingly unacceptable compromise to just accept that your program might have serious flaws which can lead to remote code execution or worse.

I think that's too strong a statement, because it applies to in-development programs. I agree with you if you're talking about released programs, but there can be benefit in leaving open the possibility of detectable flaws, serious or otherwise, while your code is still in development.

It's analogous to only compiling and running in debug mode throughout your development, and then switching to release mode for the final binary. The binary is suboptimal throughout your development process; it's too slow. But as long as the `--release` flag doesn't require any code changes, it's still a better idea than developing entirely in release mode.

Similarly, the binary could be suboptimal from a correctness standpoint, as long as removing the `--devel` flag only works when the compiler is fully happy. `--devel` could turn some borrow checking failures into warnings and still give you a runnable binary. Or it could allow leaving types underspecified in interfaces, and do an unsound type inference. Best case, it could even do runtime checks and/or coercions to establish the assumptions that the callee was compiled with.

Whether it would be worth the complexity is an open question, but it seems reasonably clear that Rust has a problem with brittleness to development-time change.

replies(1): >>42168913 #
224. maxbond ◴[] No.42167214{6}[source]
I agree.
225. ufmace ◴[] No.42167263{5}[source]
That I can agree with. I think Rust strikes a pretty good balance with it though. You can still do those things if you really need to, but it has to be marked `unsafe`. This bounds the unsafety to only the parts of the code that actually need it, allowing those parts to be examined more closely to ensure they actually are correct.
replies(1): >>42171675 #
226. sfink ◴[] No.42167408{7}[source]
What's the difference and advantage, other than less typing, of this over:

     match *Arc::new("hello").as_ref() {
      "hi" => {}
      "hello" => {}
      _ => {}
(Sorry, Rust newbie here)
replies(1): >>42168568 #
227. dijksterhuis ◴[] No.42167602{3}[source]
it’s not just languages.

i had similar problem with tensorflow for like a year or so until it “clicked” i was trying to make the framework do thing the way i wanted, instead of doing things the way the framework wanted.

seeing that in myself was a real eye opener.

now i see it in others too and it’s frustrating because, ultimately, no-one can tell them they’re doing it. they have to find out for themselves for it to make a difference for how they act.

228. James_K ◴[] No.42167954{7}[source]
> I'm asking why a developer who is comfortable with Rust borrow checker choose any other language?

Because there are other languages which don't require you to hold to a strict set of rules to maintain memory safety, as they automate memory reclamation at runtime.

> There is actually a way to get comfortable with the BC. Perhaps beginners should be introduced to those ideas rather than such negative takes.

Regardless of how comfortable you are with it, it imposes a necessary overhead, either in terms of architectural constraints or the runtime cost of reference counting which is greater than that of garbage collection.

> I keep seeing some people asking everyone else to avoid Rust based on an exaggerated account of the struggle with the borrow checker.

The borrow checker is irrelevant here. As you have mentioned, a person coding is C will have to keep to the same constraints which Rust statically enforces. There is clear reason to use a language such as Java over C. Not having to manually manage memory simplifies code and makes refactoring easier. This is true regardless of if your memory restrictions are checked or unchecked. Imagine the simplest example: a data structure containing a string. In Java, you write a class and put a string in it. The data for the string is shared and a simple copy of the pointer will suffice in the constructor. In languages with manual memory management, you have three options: the struct can own the string (or reference count it), in which case it will have to be cloned and performance will be worse than garbage collection; the struct can borrow the string, in which case you'll have to track the string's lifetime throughout the program which could cause issues later; the structure can be generic over owned and borrowed string types, in which case code will be more complicated and generic parameters will proliferate. It should be immediately apparent that the latter requires more architectural consideration than the former, and hence is a greater burden to programmers. Several of the options in the Rust case will also lead to potentially complicated refactoring later, which adds even further cost to development.

TL;DR managing memory manually takes more effort. That's why it's called MANUAL memory management, because it's not automated.

229. estebank ◴[] No.42168568{8}[source]
Patterns can be arbitrarily complex. If you have

  struct Foo {
      bar: Arc<String>,
  }
Then it's the difference between today's

  if let Foo { bar } = val {
      if &*bar == "hi" {
      }
  }
And the potential future

  if let Foo { bar: "hi" } = val {}
The more complex the pattern, the bigger the conciseness win and levels of nesting you can remove. This comes up more often for me with `Box`, in the context of AST nodes, but because I use nightly I can use `box` patterns, which is the same feature but only for `Box` and that will never be stabilized.
230. Arch-TK ◴[] No.42168913{5}[source]
If you develop C or C++ haphazardly in such a way that you leave a bunch of UB on the table during development then there's little to no chance that you'll have actually erased all presence of it by the end of development.

There currently exist no tools which with complete reliability point out all UB in your program. If any part of your program can have UB and you didn't write it with the explicit intention of not having UB in it at any point then you're going to be left with a tough situation to deal with.

I've read a lot of C in my time and there's codebases which I read and find easy and quick to review because they stick to the rules and only bend them sparingly and then there's codebases which are a pain to evaluate even the most basic parts for errors and UB.

There's no such thing as "detectable UB", there's only UB which your tools have luckily managed to detect.

Leave the UB to the people who can't avoid it, stick to safe languages when you can.

231. oconnor663 ◴[] No.42169429{4}[source]
> Then you can have dangling pointers that point to the wrong place.

Generational indexes in a SlotMap or similar should rule this out.

> Race conditions become possible.

How so?

> You need an allocator for the indices.

Yes, this usually becomes a central point of synchronization. (If you just have a SlotMap, you might not think of it as an "ID allocator", but that is half of what it is.) It doesn't have to be fully synchronous, though, and you can do fancy things with atomics.

> On rare occasions, it crashes with an invalid index.

Indexes become a sort of Weak pointer, and you get an Option when you dereference them, so you can certainly crash if you .unwrap() it or similar. You can use a Vec with "infallible" indexes if you don't want to support deletion, but if you do want to support deletion, the deleted case can bite you one way or another, no?

replies(1): >>42169534 #
232. Animats ◴[] No.42169534{5}[source]
> deleted case can bite you one way or another, no?

Right. I use a renderer where, on rare occasions, that happens. I didn't write it, but now I have to fix it, because it's abandonware.

233. FridgeSeal ◴[] No.42169898{9}[source]
I’m not blindly disliking anything.

I recognise C# as useful, but _personally _ dislike it because of the sprawling, OOP-heavy, ceremony-heavy core it encourages you to write. Half my dev friends write C# for their day jobs, half my workplaces have had me dealing with it. It’s not an opinion borne from unfamiliarity or knee jerk reaction; my distaste for MS is just the cherry on top.

234. kazinator ◴[] No.42170302[source]
It 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.

235. munksbeer ◴[] No.42170866{3}[source]
Would you be able to give an example of what you mean? What it would look like trying to write C code in Rust and then the idiomatic Rust way?
236. Const-me ◴[] No.42171675{6}[source]
> I think Rust strikes a pretty good balance with it though

I don’t like that balance.

For performance-critical things like BLAS and other low-level numerical stuff, C++ is safer than unsafe Rust because standard library and tooling (debug heap, debug allocators, ASAN, etc.) evolved for decades making the language usable despite the unsafety. Another thing, for numerical code you probably need SIMD intrinsics. They were defined by Intel, documentation and other learning resources almost exclusively target C language; C++ got them for free due to compatibility.

For high-level pieces which are not that performance critical, I use C#. Due to VM, it’s safer than Rust. Due to GC and other design choices usability is way better than Rust, e.g. in C# asynchronous I/O is almost trivial with async/await. The runtime is cross-platform, I have shipped production-quality embedded ARM Linux applications built mostly with C#. Unlike Java, it’s easy to consume unmanaged C++ DLLs using C API, or even C++ API: see that library to do that on Linux which doesn’t have COM interop in the runtime https://github.com/Const-me/ComLightInterop

237. tsimionescu ◴[] No.42173198{5}[source]
The point was to compare a fat standard library model with a model of many dedicated third party libraries. Sure, all big projects suffer from this, I'm not trying to single Rust out. But both Rust and those other projects have to learn from this that the model is bad.

Having a large and good standard library, supplied by a single trustworthy foundation, with dedicated employees that check incoming PRs, is going to become more and more important in the following years.

238. Arch-TK ◴[] No.42184081{3}[source]
Not really.

In embedded environments you're constrained by toolchain and platform but it's still a bad idea to rely on any behaviour which your compiler doesn't provide a definition for (which might be more behaviour than what your standard provides a definition for) because changes to the version of the compiler or even changes to surrounding code can trigger issues caused by reliance on UB.

It's not actually that hard to write embedded code which does not invoke UB outside of register access and even there it's possible to limit yourself to invoking behaviours which the combination of hardware + compiler does provide documented behaviour for.

(source: I've written embedded code which did not knowingly/intentionally invoke UB outside of register access and in those cases the implementation did define behaviour.)