[HN Gopher] Safety: A comparaison between Rust, C++ and Go
___________________________________________________________________
Safety: A comparaison between Rust, C++ and Go
Author : ephesee
Score : 106 points
Date : 2022-07-30 07:57 UTC (15 hours ago)
(HTM) web link (nested.substack.com)
(TXT) w3m dump (nested.substack.com)
| jayp1418 wrote:
| What about comparing to Ada Programming Language?
| TinkersW wrote:
| This would be a better article if the UB in the C++ example
| wasn't blindingly obvious-- no C++ programmer worth a damn would
| ever write this.
| pfultz2 wrote:
| Yea and C++ static analysis tools already warn for the case, so
| even for new C++ programmers where it might not be entirely
| obvious, its still easy to catch the error.
| ncmncm wrote:
| More to the point, the compiler warnings would save
| inexperienced programmers too.
| sagarm wrote:
| Yeah, agreed that a competent C++ dev would not write the code
| in the example. The charitable interpretation though is that
| errors of the same type can crop in real codebases, the example
| is just simplified for the purposes of discussion.
| pornel wrote:
| In such examples you're always primed to look for a mistake.
| It's a different situation when you have 100K+ lines of code,
| a dozen developers, and a deadline.
|
| It's a difference between "look this is Wally" (duh, of
| course) and a game of "Where's Wally?".
|
| Whenever a CVE is discussed people say that the error is
| obvious, and no good programmer would write like that. It's
| easy in hindsight.
| jrajav wrote:
| Why is this such a common defense for the peculiarities of C++?
| I see it pop up at least a couple of times, anytime C++ is
| criticized.
|
| I don't have anything against taking pride in one's skill and
| craftsmanship, but excusing a tool's failings purely on the
| basis that one needs more skill to wield it and avoid those
| failings? I want to have that same level of skill and have my
| tool multiply my skill's output to the max, not have my skill
| wasted coaxing my tool to perform correctly.
|
| If the implication is that the tool requiring more skill gives
| commensurate benefits, fair enough, but not if it's just hand-
| waving away obvious downsides.
| TinkersW wrote:
| Because the code in question isn't something a C++ dev would
| write, instead it looks like something someone who doesn't
| use the language much if at all, decided to use for this
| pretty silly comparison.
|
| It would be one thing if this required skill, but this
| example is downright silly. Some other people here have
| posted more reasonable version, that might actually occur in
| the real world(like the example with std::span)
| vitus wrote:
| I feel like a better comparison would be to use std::span (C++20)
| to mimic Rust's slice. Otherwise you might be tricked into
| thinking that adding // hey don't pass in a
| temporary auto make_appender(std::vector<int>&& suffix) =
| delete;
|
| (which turns the provided code into a compiler error under both
| g++ and clang++) is adequate to prevent the immediate class of
| issues (namely, C++ allows const Foo& and Foo&& to bind to
| temporaries).
|
| Meanwhile, it's really really easy to make std::span dangle:
| #include <cassert> #include <span> #include
| <utility> #include <vector>
| std::vector<int> append(std::vector<int>&& items, std::span<int>
| suffix) { items.insert(items.end(), suffix.begin(),
| suffix.end()); return items; }
| auto make_appender(std::span<int> suffix) { return
| [=](std::vector<int>&& items) { return
| append(std::move(items), suffix); }; }
| auto make_appender34() { std::vector<int> vec = {3, 4};
| return make_appender(vec); } int main()
| { auto append34 = make_appender34();
| assert((std::vector<int>{1, 2, 3, 4} == append34({1, 2})));
| }
| ncmncm wrote:
| That would be another tendentious example. Nobody would write a
| make_appender that takes a span argument, because it makes no
| sense.
| niekb wrote:
| The author could compile c++ with the sanitizers, i.e.
| -fsanitize=address,undefined and make a make_appender function
| that leverages perfect forwarding...:
| template<typename S> auto make_appender(S&& suffix) {
| return [perf_fwd_suffix =
| std::tuple{std::forward<S>(suffix)}](std::vector<int>&& items)
| { return append(std::move(items),
| std::get<0>(perf_fwd_suffix)); }; }
|
| see: https://godbolt.org/z/M9P4MK4a8
| ncmncm wrote:
| Disingenuous.
|
| The bad C++ code is in the very first line of the "make_appender"
| definition: capturing the closure's environment by reference is
| nonsense: It is equivalent to returning a reference to an
| argument. It is not, then, a closure at all.
|
| Using a correctly-defined make_appender would not, then, produce
| undefined behavior when you use it, with or without "move".
|
| What the author has done here was to take a too-obviously wrong
| operation, returning a reference to an argument, but dress it up
| with syntax that will look less familiar to some readers, and
| pass it off as insightful.
|
| But using a wrong function and getting wrong results is not
| surprising.
|
| Piling on more uses after, that give more wrong results, does not
| reveal anything more.
|
| When you need disingenuous arguments to make your point, it tells
| us more about your point than about the thing you are trying to
| make a point about. And, publishing anyway tells us more about
| you.
|
| Returning that fake closure should evoke a compiler warning, if
| you turn on warnings.
| dataflow wrote:
| You can add [[clang::lifetimebound]] on the suffix parameter
| and you get <source>:21:35: warning: temporary
| whose address is used as value of local variable 'append34'
| will be destroyed at the end of the full-expression
| [-Wdangling] auto append34 = make_appender({3, 4});
| pfultz2 wrote:
| You dont need annotations, cppcheck already warns with:
| test.cpp:16:45: error: Using object that is a temporary.
| [danglingTemporaryLifetime]
| assert((std::vector<int>{1, 2, 3, 4} == append34({1, 2})));
| // FAIL: UB
| ^ test.cpp:3:12: note: Return lambda.
| return [&](std::vector<int>&& items) { ^
| test.cpp:2:50: note: Passed to reference. auto
| make_appender(std::vector<int> const& suffix) {
| ^ test.cpp:4:36: note: Lambda captures variable by
| reference here. return append(move(items),
| suffix); ^
| test.cpp:15:35: note: Passed to 'make_appender'.
| auto append34 = make_appender({3, 4});
| ^ test.cpp:15:35: note: Temporary created here.
| auto append34 = make_appender({3, 4});
| ^ test.cpp:16:45: note: Using object that is a
| temporary. assert((std::vector<int>{1, 2, 3, 4}
| == append34({1, 2}))); // FAIL: UB
| dataflow wrote:
| As awesome as that is, cppcheck doesn't seem ready for
| real-world use. Literally the first invocation I ran
| resulted in this error: error: Syntax
| Error: AST broken, binary operator '!=' doesn't have two
| operands. [internalAstError] explicit operator
| bool() const { return this->get() != pointer(); }
|
| This is for a simple wrapper class that looks like this:
| template<class T> class Foo : private Bar<T> {
| public: typedef value_type *pointer; pointer
| get() const { return ...; } explicit operator bool()
| const { return this->get() != pointer(); } };
|
| Another example: void foo(uintptr_t const
| (&input)[2]) { if constexpr (sizeof(uintptr_t) ==
| sizeof(int) && sizeof(long long) == 2 * sizeof(int)) {
| long long value; memcpy(&reinterpret_cast<uintptr_t
| *>(&value)[0], &input[0], sizeof(input[0]));
| memcpy(&reinterpret_cast<uintptr_t *>(&value)[1],
| &input[1], sizeof(input[1])); } }
| error: The address of local variable 'value' is accessed at
| non-zero index. [objectIndex]
| memcpy(&reinterpret_cast<uintptr_t *>(&value)[1],
| &input[1], sizeof(input[1]));
| ^
| dannymi wrote:
| >The bad C++ code is in the very first line of the
| "make_appender" definition: capturing the closure's environment
| by reference is nonsense: It is equivalent to returning a
| reference to an argument.
|
| If it is so bad, it should (in the sense of how things would be
| in an ideal world) not compile.
|
| >It is not, then, a closure at all.
|
| It is a closure because all variables are closed-over and there
| are no free variables in the lambda body anymore. That is the
| definition of closure.
|
| >But using a wrong function and getting wrong results is not
| surprising.
|
| In an ideal world, there should be a compilation error. (There
| is in Rust)
|
| The majority of what's wrong in C++ is that it lets you do
| nonsensical (even dangerous) things, most of the time without
| even a warning (and not because it's technically impossible to
| warn--it just didn't occur to them). It's okay to acknowledge
| that--it's a product of its time.
|
| >Returning that fake closure should evoke a compiler warning,
| if you turn on warnings.
|
| That "should" tells me all I need to know. In the end either
| safety is important, or it isn't. Choose accordingly.
| keithalewis wrote:
| "Doctor, it hurts when I do that!"
| wruza wrote:
| Pain is a strong warning that prevents [further] trauma.
| This analogy defeats its own purpose.
| jstimpfle wrote:
| >> In the end either safety is either important, or it isn't.
| Choose accordingly.
|
| Not to disagree with your post but this comes across as very
| black-and-white.
| dannymi wrote:
| To expand:
|
| There's a prioritized list of requirements in engineering.
| Use the right tool for the job.
|
| Rust has: (see https://hackmd.io/@rust-ctcft/r1plN4You#/5 )
|
| 1. Safety
|
| If you don't need that at the first slot, then the biggest
| strength of Rust doesn't apply to your problem (and you
| would waste time having to track lifetime parameters more
| than necessary to solve your problem).
| pdimitar wrote:
| Big problem in these discussions is what should be
| prioritized.
|
| To me, with the 20 years of experience I have in 8
| programming languages, I'll always include safety.
| Especially having in mind that it's nearly zero-cost in
| terms of runtime performance.
|
| So to me not choosing safety is a strong sign that I
| don't want to work with the people who practice that.
| estebank wrote:
| There's also another item in that list, and that is
| performance. As soon as you slightly relax that one, Rust
| does become massively easier. A well placed .clone() or
| Arc makes the rest of the code performant enough and
| easier to understand, which makes the equation of
| choosing Rust over other alternatives in spaces that
| aren't necessarily systems programming less problematic.
| unsafecast wrote:
| > If it is so bad, it should (in the sense of how things
| would be in an ideal world) not compile.
|
| Yes, C++ can be a bad solution to a lot of problems, and
| that's okay. Use rust if you need a machine guarantee for
| memory safety (or you just like the language), you can use Go
| if you just don't care about that at all and want the
| language to take care of it. But you can use C++ for non-
| critical software that needs to be fast (games come to mind).
| Rust can be too much of a mental overhead than it's worth for
| some.
|
| That closure is just not a good example. Nobody would write
| this, because when writing C++ code you _do_ think about
| whether you want a reference or not. Sure, a lot of bugs can
| happen but this is not, in my opinion, one of them.
|
| If you want to prove a point, prove it fairly.
|
| Note: I don't use C++ anymore, and I don't like it very much
| for other reasons.
| fjdbeb76 wrote:
| > Rust can be too much of a mental overhead than it's worth
| for some.
|
| Rust has the least mental overhead of any language I've
| ever used. The compiler literally takes all the mental
| overhead away.
| CrendKing wrote:
| The fact that you may know that make_appender definition being
| bad does not mean every C++ user knows as well. It's also
| impossible for you to know ALL the possible bad, UB leading C++
| code out there.
|
| I think the point the author tries to make is that, while C++
| and Rust are probably "the same" for the most skillful and
| disciplined programmers (such as you), for average human, Rust
| just catches way more errors they make for them early. An
| extreme analogy would be trying to climb Everest all by
| yourself vs with a professional guide.
| ncmncm wrote:
| Literally anybody can turn on warnings and heed the results.
| benreesman wrote:
| Not to disagree necessarily, but if you (give me a little
| rope) bucket languages that are hard to get past the compiler
| but more often correct when you do (Rust, Haskell), and
| languages where it's pretty easy to get _something_ past the
| compiler and tweak it until it works well enough for your
| purposes (JS, C /C++), the tweak-it-until-it-kinda-works
| languages are _fucking killing it_ on adoption.
| gmnash wrote:
| JS and C++ are killing it on adoption because they had near
| monopolies for an extended period of time in their
| respective areas (browser, native higher level language).
| They are popular despite their obvious (some in hindsight)
| shortcomings due to lack of alternatives in the same
| categories.
| zozbot234 wrote:
| > it's pretty easy to get _something_ past the compiler and
| tweak it until it works
|
| That's just as true of Rust if you use clone(), Rc<> and
| RefCell<>. You just have to familiarize with a few
| boilerplate patterns, and the best part is you're only
| trading off a modicum of performance while preserving
| safety. But Rust can work quite well as a language for
| exploratory programming.
| grumpyprole wrote:
| > the tweak-it-until-it-kinda-works languages are fucking
| killing it on adoption.
|
| Industry clearly prioritises speed of delivery above all
| else. Security, reliability and maintenance are future
| problems (that would be nice to have).
|
| However, in order to gain the power to reason about our
| code and be able to prove the correctness of various
| properties (what a typechecker does), we have to program
| with more restrictive models. This has been argued many
| times before (e.g. Dijkstra's structured programming).
| Haskell and Rust are just two of many examples. My
| favourite is regular expressions, choose actual proper
| regexes and you have guaranteed O(n) execution, choose
| Perl/Python "Regex" and you have a potential security hole.
| davedx wrote:
| I dunno man. I've done C, C++, JavaScript and TypeScript
| professionally for significant chunks of my career, and the
| trend that I've observed has overwhelmingly been towards
| stricter compilers. For example in the front-end world,
| TypeScript has absolutely exploded in adoption. Everyone
| could be still using JavaScript, but companies from
| startups to huge corporates have explicitly decided they
| want compile type safety -- often in "only" front-end code.
|
| I suspect that the adoption of Rust as a system language is
| going slower because that's just the natural pace of
| embedded/systems development, not because of anything
| intrinsic to Rust. There are now 30k+ lines of Rust code
| [1] in the Linux kernel. C++ can't claim that.
|
| [1] https://www.phoronix.com/news/Rust-v6-For-Linux-Kernel
| benreesman wrote:
| Oh I think we probably see largely eye-to-eye. My weapon
| of choice when there are no other constraints is Haskell,
| and one reason I really like Rust is that I can get a lot
| of the Haskell features I like in a highly-performant
| setting. Most of the C++ I maintain these days is in
| whole or in part generated by Haskell. And if I have to
| write something fast by hand and it doesn't need to link
| to stuff I need, I reach for Rust generally.
|
| I was also unaware that Rust has such significant
| penetration in the Linux kernel, and that's a place where
| I can see it really shining.
|
| My first comment in this thread was something to the
| effect that Rust has _tons of great stuff to offer_ , and
| that the memory safety argument is actually weaker than
| people think and probably not the only thing people
| should talk about.
|
| The resulting gang-tackle is just one more data point
| that the community is still too small and evangelical for
| me to want to get involved past my proprietary Rust
| stuff.
| davedx wrote:
| Ha, Rust community is very energetic, but IMHO they
| largely put that energy to good use!
|
| I'm using it for a new project and honestly I'm using it
| more for the modern tooling and easy C interop than the
| safety features, but I'm a fan overall. Think it's a
| really good language.
| unsafecast wrote:
| > Most of the C++ I maintain these days is in whole or in
| part generated by Haskell
|
| Can you expand on that? I'm currently researching
| something similar but lower level & lisp instead of
| Haskell. It would help to see some existing examples to
| figure out if it's worth it or not.
| benreesman wrote:
| Ideally we'll just be able to open source it soon!
|
| Basically we have a nice Haskell DSL for generating
| arbitrary C++, and we deal with lots of code you wouldn't
| want to write by hand (big nested switch statements and
| other kinds of state-machine logic, choices about loop
| unrolling, lots of template overloads, SIMD intrinsics
| that require immediate values, etc. etc.) so we write
| Haskell that generates C++ and feeds it to e.g. `clang`.
|
| Some of this is directly in Haskell, and some of it is
| little compilers mostly done using Megaparsec. It's a
| really nice approach where it fits!
| unsafecast wrote:
| Sounds great, that's what I was imagining too. Is there a
| way I can find it when/if you open the source?
| bhaak wrote:
| Javascript's adoption has much less to do with its language
| features but with its unique positioning.
|
| There was (and still isn't) a competitor that can compete
| on the same level with Javascript in the browser.
| [deleted]
| benreesman wrote:
| I was trying to be a little more diplomatic in my sibling reply
| because I've locked horns with the Rust community before and
| not enjoyed it, but you're not wrong.
| jstimpfle wrote:
| std::vector<int> suffix{3, 4}; auto append34 =
| make_appender(suffix); // Version 1: test will work
| auto append34 = make_appender({3, 4}); // Version 2: test will
| fail
|
| I was in a mood to type the examples but I accidentally made
| Version 1 because I had started with the author's first
| example. I wondered where the problem was and couldn't find any
| (neither running nor reading the code) until I noticed the
| author had changed to Version 2.
|
| The problem is "obvious" in hindsight but one of the problems
| with C++ is that it makes certain things too
| implicit/convenient. I get that references are a staple feature
| in C++ but it's not at all obvious from the calling location
| that Version 2 is a bug. To see that, you have to navigate to
| the callee (finding which might be hard enough alone without a
| very solid IDE), and make sure that it's not capturing the
| argument by reference or not doing anything unsafe there.
|
| It's often a problem when a seemingly "value" argument is
| turned into actually a pointer-to argument at the callee. There
| are other languages that have this too, and I've never liked
| it.
|
| As someone who doesn't regularly code in C++ but has a solid
| understanding of the basics, I wonder why C++ ever allowed to
| have a reference parameter be called with a temporary? To me it
| feels like "References have value syntax but pointer semantics
| BUT you should program like it had value semantics"? Which to
| me would be exactly a premature optimization that is looking
| for trouble.
| benreesman wrote:
| Again, this isn't a right/wrong thing. Rust moves by default
| (and a lot of people find "=" a weird pun for that), C++
| copies by default and has rvalue-references and an explicit
| `std::move`.
|
| If you want copy in your C++ lambda, you start it `[=](...)
| {...}`, if you want take a pointer in your C++ lambda, you
| start it `[&](...) {...}`, if you want something trickier you
| do trickier stuff.
|
| Rust opts you in to the nitpicky static analyzer and you have
| to opt out with `unsafe`, in C++ you have to opt in with e.g.
| `clang-tidy` or some annotations.
|
| They are remarkably similar, just with different defaults.
| pjmlp wrote:
| There lies the problem with C++ state of affairs, most
| defaults are wrong.
|
| It doesn't help that those of us that care about enabling
| the right defaults are a tiny minority as per C++ surveys.
|
| https://blog.jetbrains.com/clion/2021/07/cpp-ecosystem-
| in-20...
| tialaramex wrote:
| > They are remarkably similar, just with different
| defaults.
|
| I'm going to lead with this, because I think it's most
| important: Culturally there's a world of difference. Safety
| is a part of Rust's _culture_. "Culture eats strategy for
| breakfast".
|
| Take sorting. In C++ the default sort is unstable, while in
| Rust the default sort is stable, that's just those defaults
| you mentioned (each has both kinds), although the choice
| speaks to culture. But look closer, in C++ the sort has
| _undefined behaviour_ if your type isn 't totally ordered.
| In Rust you can't sort the partially ordered types without
| saying how to order them fully. Still, in both languages we
| can write a custom order, so what happens then? In C++ if
| your custom order is nonsense you get... undefined
| behaviour. In Rust sorting won't necessarily _work_ with a
| nonsense custom order but the behaviour remains well
| defined.
|
| > Rust opts you in to the nitpicky static analyzer and you
| have to opt out with `unsafe`
|
| Unsafe gives you a small number of dangerous "super powers"
| needed to write efficient low-level code, it does _not_ opt
| out of the borrow checker 's analysis, or indeed most other
| checks. This misconception makes me wonder how much of what
| you've written is conjecture rather than practical
| experience.
| benreesman wrote:
| The sibling says that C++ has the _wrong_ defaults, full
| stop.
|
| Well in Rust the default `HashMap` uses a cryptographic
| hash, and you see it _everywhere_ , it's the de facto
| community "default". In C++ the community "default" is
| `absl::flat_hash_map`/`folly::F14`, which use SIMD to
| compare a whole stripe of key-prefixes simultaneously.
|
| I want different defaults for different programs, but the
| idea that it's esoteric to ever want an associative
| container within arms reach that _fucking demolishes_ the
| other one is, ugh, God I want to like Rust even more than
| I do but this "we're right and everyone else hasn't seen
| the light" routine is infuriating and pushes me at least
| away.
|
| My parent comment is trying to emphasize that this isn't
| a right/wrong thing, different tools for different jobs.
| And people are just like: "nope, everything but Rust is
| _wrong_ ".
| jjnoakes wrote:
| Why do you think that a language with better details
| would prevent you from opting in to whatever non-default
| behavior you need?
|
| Having the "right" defaults is better for everyone. Folks
| who don't know or care get a good, safe default with no
| undefined behavior or unexpected danger, and folks who
| know better can opt into something that fits their needs
| explicitly.
|
| Seems ideal to me.
| jjnoakes wrote:
| > a language with better details
|
| Whoops - that should have said "a language with better
| defaults"...
| ncmncm wrote:
| It might be more reasonable to default to a crypto hash,
| but in fact it _also_ does not actually improve security.
|
| So, the trade is just: slow for nothing.
| mattgreenrocks wrote:
| I like and use both C++ and Rust. I also have plenty of
| bones to pick with both languages.
|
| However, I've never gotten the sense that Rust itself
| promotes the idea that "everything except for Rust is
| wrong." I also don't read much on the Internet these
| days, and I'm not doing so, probably avoid much of the
| hype that people are pushing about Rust.
|
| Since it has been established as Hot New Thing, there are
| huge social incentives tied up in promoting it.
| CryZe wrote:
| > In C++ the community "default" is
| `absl::flat_hash_map`/`folly::F14`, which use SIMD to
| compare a whole stripe of key-prefixes simultaneously.
|
| Just to be clear, the Rust HashMap does the same thing.
| benreesman wrote:
| TIL. Thanks for letting me know that: https://doc.rust-
| lang.org/src/std/collections/hash/map.rs.ht...
|
| I don't know how I missed this, is the Swiss port a
| fairly recent development?
| tialaramex wrote:
| Hashbrown (the Swiss Tables implementation) replaced the
| previous HashMap implementation in July 2019.
|
| The port is a little older, 2018. The idea was famously
| explained at CppCon 2017, I don't know whether Google had
| published on Swiss Tables before that year.
| snovv_crash wrote:
| You can also 'const' your std::vector when you declare it and
| it won't work. Any linter or having warnings turned on will
| catch these issues.
| jstimpfle wrote:
| const doesn't make a difference in this case. It's about
| the passed-by-reference object being destroyed after the
| function returns. That is because the object was passed as
| a temporary.
| crabbygrabby wrote:
| It's possible the author actually made this mistake, and
| because c++ is C++ they didn't realize why/how. Not a lot of
| programmers have a good handle on c++, maybe even most don't...
| Hence these other languages.
| ncmncm wrote:
| If the author deliberately chose to compile with warnings
| turned off, in order to present an example that would crash,
| then that tells us more about the author than about the
| point.
| [deleted]
| roca wrote:
| A function returning a value that depends on the lifetime of
| the function's parameter is not crazy at all. Every class
| getter method that returns a reference to a member of the class
| does this.
| benreesman wrote:
| Sure. But returning the address of a stack-allocated object
| is (usually) broken.
|
| There isn't a right and wrong here: sometimes you want to opt
| in to the check for that, sometimes you want to opt out of
| it.
|
| Sometimes I _actually do_ want to fuck with addresses on the
| stack in weird, potentially architecture-dependent ways, it
| 's rare but it happens.
|
| I happen to think that Rust's linear/affine typing is by far
| the most usable low/zero-cost memory management model that
| anyone has demonstrated at scale and a real achievement in
| practical computer science, but it comes at a pretty serious
| cost in `Box`-this and `Arc`-that and `Rc`-other-thing and
| generally the borrow-checker being a PITA about some stuff
| we're used to doing.
|
| Rust is very cool and I use it, but the "using C/C++ is
| fucking strangers without protection"-vibe got old _years_
| ago.
| tialaramex wrote:
| > returning the address of a stack-allocated object is
| (usually) broken.
|
| What matters is the lifetime, where the object lives is a
| rule of thumb for guessing lifetime that results from C++
| trauma.
| DmitryOlshansky wrote:
| The fact that the GC "owns" all of the memory happens to help a
| lot with lock-free stuff.
| alrlroipsp wrote:
| Screenshot absolutely make me *PZs%0>>,-
| phendrenad2 wrote:
| Can we throw Python in too? Python has more safety than any of
| these languages, yet it's always left out of these discussions.
| pdimitar wrote:
| You're joking, right? A dynamically-typed language has more
| safety?...
| aliqot wrote:
| Is this satire?
| benreesman wrote:
| Rust has a lot of great qualities that C++ lacks, but comparing
| `rustc` to `gcc` or `clang` on move-semantics checking is just
| kind of silly these days. `rustc` has `clang-tidy` built in.
|
| `clang-tidy` is not letting you mutate or even access that moved-
| from "suffix" object without throwing an error.
|
| It's annoying that you need `clang-tidy` and ASAN and shit to get
| comparable runtime safety even in greenfield C++, but that's not
| why to prefer Rust.
|
| Prefer Rust because of a better traits system and pattern
| matching and syntax for `Maybe/Either` and a consistent
| build/library story and a number of other things.
| blub wrote:
| Objectively speaking, Rust does not "have a lot of great
| qualities that C++ lacks". Any new feature or improvement has
| its pluses, but also its minuses.
|
| * Rust has traits, but does not support OOP. Architectures
| where OOP is particularly effective are proving to be a
| significant challenge for Rust - GUIs are the obvious one, but
| also game development Rust projects have to invent new
| approaches.
|
| * Option/Result make the code flow obvious, but having to
| return them in nearly all function calls is tedious. Rust has
| had several attempts at alleviating this problem, but still
| hasn't matched the convenience of exceptions.
|
| * match is IMO syntactic sugar and its benefits for correctness
| are being oversold. The fact that one is basically obliged to
| use it leads to code that's sometimes too deep. Exceptions
| would cut through this error-handling noise, if they were
| available.
|
| * The build story is convenient, but this had the unintended
| effect of encouraging dependency explosion. Adding typical
| crates results in dozens of transitive dependencies being
| included in a project.
|
| The notable improvement that Rust brings to the table is
| machine-verified memory safety with C++-like performance, but
| that of course comes at the cost of having to adapt code to
| what the borrow checker understands. If one _needs_ the
| feature, then the cost is worth paying, if not, Rust is more of
| a personal choice than an inevitable conclusion.
|
| And finally, perhaps Rust's biggest sin is that it's big, it's
| complex and there's no end in sight to the complexity spiral,
| just like for C++. I can only imagine the chagrin of the Rust
| community as they see themselves competing with Go for many
| projects where performance is not absolutely critical and often
| being second choice exactly because of this complexity.
| ncmncm wrote:
| More to the point, Rust _lacks key features_ I need to
| capture essential semantics into libraries. So, the libraries
| I could write in Rust would be less powerful than libraries I
| can write in C++.
|
| _Among_ common uses for these more powerful features is to
| make misuse of the library into a compile-time error. Coding
| the library in Rust, if possible at all, would mean failing
| to prevent these usage errors.
|
| The point here is that C++ puts more power in the hands of a
| library writer, and both the responsibility and capability to
| enforce safety in _uses_ of the library. Rust jealously
| reserves maintaining safety to the compiler alone.
| blakehawkins wrote:
| And way, way better compiler errors thanks to a sane generics
| system
| bbojan wrote:
| I think you meant thanks to a sane(r) macro system? Both Rust
| and C++ use monomorphisation for generics, I believe shitty
| compiler errors are due to C++'s templating.
| throwaway17_17 wrote:
| I'm what way can you have 'generics' in C++ that are not
| based on templating? I am almost certain that any
| implementation of anything 'generic' templates are
| inherently involved. Maybe I'm wrong about what you mean by
| generics though.
| estebank wrote:
| There are concepts now which are close enough to Rust's
| traits.
| benreesman wrote:
| And much better compiler errors. `rustc` about leads the pack
| on error messages of all the languages I use regularly.
| [deleted]
| estebank wrote:
| Your point is a fair one, but defaults matter. There's little
| reason for clang-tidy not to be part of the default clang
| invocation by now, other than an aversion to producing new
| output for existing projects (that you could argue are already
| "broken"). Unless clang-tidy has false positives, in which case
| the comparison isn't apples to apples then.
| benreesman wrote:
| Oh I completely agree that defaults matter, and I wouldn't
| advise anyone to go start some big C++ project unless they
| had a very good reason to.
|
| But Rust vs. C++ is not an apples-to-apples comparison in
| this regard: Rust got a clean slate and was willing to cut
| ties with engineer-millenia of existing high-value software
| to do it.
|
| In a perfect world Rust wouldn't be ambivalent at best and
| hostile at worst to C++ interop, c'est la vie.
| estebank wrote:
| Rust isn't hostile about C++ interop, it's that between
| native interop (which requires dealing with templates and
| memory unsafety) and safety, Rust prioritized safety, while
| Carbon is trying the alternate approach. The behavior of
| C++ is simply hard to interface with while providing the
| assurances Rust gives you.
|
| Edit: how many languages have native C++ interop that
| supports the whole language? Would love to hear of any.
| benreesman wrote:
| I said ambivalent to hostile at worst, and I'm sorry, it
| is. I write FFI to C++ in Rust, Python, and Haskell
| practically every week, and Rust <-> C++ sucks.
|
| Slap it in an `unsafe` block, fine. But let me move a
| `std::vector<std::string>` into my `unsafe` block easily.
| Erase the types, fine. But let me call `v.at(idx)`.
|
| Python takes C++ interop seriously, which is why
| Tensorflow and PyTorch and all the other people trying to
| script gigantic, extreme-value C++ codebases use it. Try
| `pybind11` sometime, it's night and day.
|
| Edit to reply to edit: `pybind11` supports an _absurd_
| amount of C++ out of the box with completely natural
| semantics and a very modest performance penalty. So,
| Python.
| touisteur wrote:
| I wouldn't say python takes C++ interop seriously, it's
| more like the pybind11 people are amazing at what they do
| and found a way to slice the problem neatly. But yes,
| it's night and day, pybind11 is a godsend.
| estebank wrote:
| Thanks, I'll look at pybind11. Have you looked at cxx by
| any chance?
| benreesman wrote:
| I have. `cxx` and `autocxx` and `bindgen` and `cbindgen`
| are, better than nothing I guess? But they're all flakey
| and have weird corner cases (and crash sometimes! I'm
| looking at you `cbindgen`!) and don't handle containers
| well if at all and just, ugh.
|
| I always end up saying fuck it and `extern "C"`-ing
| everything. It would be completely possible to make these
| tools work well, but the Rust ethos is "rewrite
| everything, pure Rust", at least in large parts of the
| community, and so these projects kind of never get
| totally dialed in.
| zozbot234 wrote:
| > But they're all flakey and have weird corner cases and
| crash sometimes!
|
| Did you report those crashes? There's also crubit these
| days, mostly combining the autocxx and (c)bindgen
| approaches. (It's a very new effort, which is why it's
| being kept separate from these more established ones.)
| dureuill wrote:
| was not aware of crubit, so thanks for posting. i'm
| trying to follow closely what's happening in this space
| infamouscow wrote:
| It's not just C++ interop. We chose Zig because writing C
| library bindings for Rust is bewildering.
| TillE wrote:
| Given the vast C++ ecosystem (including pretty much every
| big game engine), there _are_ still a ton of very good
| reasons to start a new big C++ project.
|
| At least until Carbon is ready! But that's a few years away
| at the least.
| pjmlp wrote:
| They do, that is why any C++ shop where code quality is
| relevant has a DevOps team that cares about the right
| defaults being enforced on th CI/CD pipeline.
|
| Devs that don't care only get to build on their own
| computers.
|
| It isn't fullproof, but definitely helps to tame some
| cowboys.
| dataflow wrote:
| > Unless clang-tidy has false positives, in which case the
| comparison isn't apples to apples then.
|
| Confused... so you're suggesting Rust's checks are somehow
| free of false positives? Doesn't the halting problem get in
| the way? One Rust-specific example: https://www.reddit.com/r/
| rust/comments/nr7a33/is_the_borrow_...
| estebank wrote:
| There's a difference between code that used to compile no
| longer compiling because of an incorrect lint, and code
| that was never accepted. Rust is restrictive and gets less
| so over time. C and C++ _need_ to become more restrictive
| over time, but that 's a more traumatic direction.
| dataflow wrote:
| What you're actually arguing seems to be "why I like Rust
| more than C++", not arguing why "clang-tidy has false
| positives, and thus the comparison isn't apples to apples
| then".
|
| Clearly the positives can be just as false in Rust as in
| C++. Your actual objection is that anyone arguing that
| any feature of Clang can measure up to the corresponding
| feature of Rust _at present_ is automatically
| disqualified from making that argument because... Clang
| 's past "taints" its present? Like an original sin of
| sorts, but in programming? ("Apple" forbidden against
| comparison?)
| estebank wrote:
| It's not about an original sin, it's about the
| practicalities of changing the default behaviour without
| pissing off your users.
| dataflow wrote:
| Sure, but again, that's an argument for why you dislike
| C++, not an argument for why false positives in that
| Clang-Tidy check somehow disqualify it from being
| compared to the corresponding checks in Rust, especially
| when they both have false positives.
| ufmace wrote:
| I don't think it's about being tainted. It's that there's
| a bunch of C++ code out there already in Production in a
| ton of places that doesn't pass those checks, and may or
| may not actually be safe. It would be a ton of work that
| may not produce any end-user value for any C++ project to
| switch over to that.
|
| Meanwhile, Rust has always had those checks, so there
| can't be any Rust code in Production that doesn't pass
| them that would be painful to switch over.
| benreesman wrote:
| I find it so weird that the Rust community is borderline
| evangelical about memory safety when a) it's not actually
| memory safe once you start doing heavy shit b) modern C++
| is quite memory safe and c) there are so many other great
| reasons to like Rust.
|
| Memory safety in serious systems software is something that
| you approach asymptotically and/or probabilistically. Rust
| makes it _easier_ to be memory safe in a lot of scenarios,
| at the cost of the father-knows-best borrow checker, but a
| crashed program is a crashed program whether I dereferenced
| a null pointer or was poking around in a slice with multi-
| byte Unicode characters in it. And that 's before you get
| to `rg unsafe` on your favorite industrial-strength Rust
| codebase.
|
| Rust is cool for so many great reasons that get talked
| about so little because everyone seems too busy acting
| superior about memory safety. Talk about traits! Or Cargo!
| Or the cool async stuff! Anything but another lecture on
| memory safety.
| dannymi wrote:
| >a crashed program is a crashed program whether I
| dereferenced a null pointer or was poking around in a
| slice with multi-byte Unicode characters in it
|
| Most of the biggest advances in software engineering are
| because of increased modularity. One of the best
| traditional ways to increase modularity is the ability to
| define and call functions. But any isolation between
| these "function" modules is only possible if you can at
| least factor out things into a function mechanically
| without introducing crashes (for example because of
| memory unsafety--modularity would fly out of the window
| right there).
|
| >Rust is cool for so many great reasons that get talked
| about so little because everyone seems too busy acting
| superior about memory safety. Talk about traits! Or
| Cargo! Or the cool async stuff! Anything but another
| lecture on memory safety.
|
| It's better not to dilute the message. All these other
| things are nice-to-have gimmicks. But the memory safety
| is a game-changer. It does no good to advertise 230
| features at the same time. No one will remember.
| Advertise the killer feature. And that's the lifetime
| stuff, which gives you memory AND THREAD safety.
| pjmlp wrote:
| Rust's thread safety only applies to the special case of
| those threads accessing in process data segments.
|
| Rust's type system can do very little to help when those
| threads are accessing the same record on a database
| without transactions, OS IPC on shared memory,
| manipulating files without locks, handling hardware
| signals, handling duplicate RPC calls,...
|
| Yeah but that ultimately requires an unsafe block, kind
| of true, except no one reads the code of all crates they
| depend on, and the direct dependencies might be safe in
| what concerns the direct consumers.
| bluejekyll wrote:
| This is a point that you constantly bring up in these
| threads, do you think most developers believe that data
| race safety should extend beyond the bounds of the
| process?
|
| One thing that Rust's type system does allow you to do is
| define a consistent manner in which to access external
| systems, even add types that will mimic the same safety.
| Is it perfect? Will it protect you from a different
| process working against the DB? Will it enforce things in
| the other process? No. But will it give you higher level
| semantics to be able to construct a better model for
| operating against that external system? Yes.
| pjmlp wrote:
| Yes, when they care about data consistency in distributed
| systems.
|
| Maybe many Rust devs don't care.
| tialaramex wrote:
| This has been a recurring theme from you, but in the
| cases you're describing the risk is only a race condition
| (no general solution is possible) and not a data race
| (which safe Rust is able to deny by design). These are
| categorically different problems.
| pjmlp wrote:
| Because it is a recurring theme to ignore the other kind
| of race scenarios when promoting Rust's type system.
| tialaramex wrote:
| People have to consider race conditions _anyway_ ,
| they're part of our world. For example if you use git's
| ordinary --force to overwrite certain changes that's
| subject to a TOCTOU race, which is why force-with-lease
| exists. Even in the real world, I once opened a bedroom
| window to throw a spider out onto the garden below and a
| _different_ spider came in through the window in the
| brief interval while it was open - exploiting the "open
| window to throw out spider" race opportunity.
|
| Data race isn't just "Oh it's just a race condition" or
| Rust wouldn't care, data races destroy Sequential
| Consistency. And humans need sequential consistency to
| reason about the behaviour of non-trivial programs. So,
| without this writing concurrent software is a nightmare.
| Hence, "fearless concurrency".
|
| You won't destroy sequential consistency by having non-
| transactional SQL queries. Try it.
| lenkite wrote:
| Is Rust really "fearless concurrency" ?
|
| Considering the number of deadlock issues encountered by
| folks using async Rust, I think "fearless concurrency" is
| misinformation by Rust evangelists.
|
| Deadlocks in production Rust microservice:
|
| https://blog.polybdenum.com/2022/07/24/fixing-the-next-
| thous...
| pjmlp wrote:
| I have tried plenty of times, and seen not so happy train
| travelers with the same ticket for the same place on the
| same train, hence why bring it up all the time.
|
| It is obvious it is a subject that is irrelevant in the
| Rust community.
|
| Who needs consistency in distributed systems when
| multiple threads from the same process are accessing the
| same external data without coordination.
| tialaramex wrote:
| > Who needs consistency
|
| Programmers do. Programmers are human and so can't reason
| about the behaviour of non-trivial programs without
| sequential consistency.
|
| If I was trying to debug software which sometimes
| mistakenly issues people duplicate tickets, I think I'd
| want to be able to reason about how the software works,
| and that's not going to be possible if it doesn't even
| exhibit sequential consistency.
| ncmncm wrote:
| Your argument amounts to "Rust prevents X, so X is
| important. Rust cannot prevent Y, therefore Y is less
| important."
| benreesman wrote:
| Rust isn't a startup business or (one hopes) a religion,
| or an MLM, or a home for sale. It's a useful tool among
| many.
|
| Why do people say things like: "It's better not to dilute
| the message"? Better for who? That's sales/marketing
| language, not engineering language.
|
| "The message"? Pardon my Francais, but WTF?
| dannymi wrote:
| >Why do people say things like: "It's better not to
| dilute the message"?
|
| >Better for who?
|
| Better for everyone.
|
| When talking about a new thing, it would be really silly
| to emphasize how nice the logo is, how nice the package
| it comes in is, look at the awesome tape the box is
| closed with etc. If I turn the product off it even turns
| off! Look at the nice rounded corners of the device!
|
| It even can do async! Just like Javascript and .NET.
|
| Who cares!
|
| What is the main strength of the tool, the pain point it
| was made to eliminate? Lead with that.
|
| > That's sales/marketing language, not engineering
| language.
|
| Leading with the actual technical novelty that actually
| advances the state of the art in production compilers is
| marketing? Well, I guess it's good marketing in a way.
|
| The user will find cargo on their own in 5 minutes.
| ChadNauseam wrote:
| Even if rust wasn't memory safe, being C++ with traits
| and ADTs would be enough for me to use it. Those are
| important safety features!
| benreesman wrote:
| I mean "message" how you want, HN is hosted in a free
| country.
|
| But N=1 for you: as a serious polyglot user of Rust who
| knows it well and uses it all the time: this shit is a
| _huge_ turnoff. It 's a programming language. On a long
| enough timeline all the motivated hackers will end up
| knowing many programming languages well, they all have
| pros and cons.
|
| Trying to boil important engineering decisions down to a
| tweet so that we can stay "on message" comes off like
| something someone would do if they were selling books or
| training or consulting services attached to a technology,
| which a priori gives them an agenda other than giving
| good advice.
|
| So to keep it short: help people pick the right tool for
| the job without an agenda.
| bluejekyll wrote:
| Rust is a programming language. It's the right tool for
| the job of programming. It happens to have a lot of
| features that make it a very good programming language.
|
| I think you're wrong about the language and community,
| though. It's killer feature is it's safety, be it memory,
| data race, or type. These are the reasons I was
| interested in learning the language. The fact that the
| tools make that easier is why I was able to struggle
| through the new concepts and actually be able to build
| useful things with it.
|
| If the fact that people enjoy something as a general
| community turns you off, that's not the community's
| problem.
| jamincan wrote:
| I think you have a point. Rust is primarily focused on
| being a systems language, and memory safety is the killer
| feature it brings to the table in that domain. But we
| know that Rust is being used in areas where its qualities
| as a systems language are less important.
|
| Why, for example, would a Python developer pick up Rust?
| Probably because of the really strict typing addressing a
| major pain point for most Python developers and the trait
| system being somewhat analogous to Protocols, which any
| Python developer who has chafed with the dynamic typing
| is almost certainly already familiar with. With good
| library support for interfacing between the two, it's a
| more natural coupling than most people would think on the
| face of it.
|
| That said, while I don't think a Python developer reaches
| for Rust because of memory safety, I do think it's still
| an important factor as it provides the guard rails that
| make it so someone who has primarily used a GC language
| and not had to concern themselves as much with managing
| memory can start using Rust knowing that the compiler is
| not going to let them accidentally shoot them in the foot
| when it comes to memory management.
| nyanpasu64 wrote:
| rustc rejects the resulting program if you mechanically
| factor out a function accessing &mut self, into a
| function holding mutable borrows to half the fields
| calling another function which access the other half of
| fields (or vice versa, the caller holding &field calling
| a method mutating other fields). This requires the more
| complex transformation of passing individual fields into
| the subfunction (more work, but sometimes easier to
| read), or waiting for Rust to add partial borrows. Note
| I've never actually hit this case myself, though I've
| heard it's an issue people run into.
| BoardsOfCanada wrote:
| I think memory safety is the killer feature of rust, and
| has become so because people see the real world problem
| it's solving, more than through evangelicalism. We'll see
| in a few years when more "heavy shit" has been
| written/rewritten in rust. My prediction is that they
| will have significantly fewer memory safety issues than
| comparable c++ "heavy shit".
| pjmlp wrote:
| Business also needs to care,
|
| > Many years later we asked our customers whether they
| wished us to provide an option to switch off these checks
| in the interests of efficiency on production runs.
| Unanimously, they urged us not to--they already knew how
| frequently subscript errors occur on production runs
| where failure to detect them could be disastrous. I note
| with fear and horror that even in 1980, language
| designers and users have not learned this lesson. In any
| respectable branch of engineering, failure to observe
| such elementary precautions would have long been against
| the law.
|
| -- C.A.R Hoare on his Turing award speech in 1981.
| benreesman wrote:
| From where I sit the killer feature of Rust is that a
| bunch of amazingly cool software is written in it,
| especially in the terminal. I'm a big terminal guy, and I
| can't think off the top of my head of anything I use
| constantly that _isn 't_ written in Rust. `rg`, `fzf`,
| `zoxide`, `bat`, `viddy`, the list goes on and on, I
| fucking love the shit people are writing in Rust.
|
| And I think that should be the killer feature of a
| language: that cool software is written in it and is
| continuing to be written in it. This is a killer feature
| shared by Rust and C++ and these days to be serious about
| performant software in diverse settings, you pretty much
| have to know both well.
| jamincan wrote:
| Terminal programs are one area where Rust's strengths
| seem to align (very good CLI libraries/parsing, error
| management, and concurrency) and weaknesses are less
| relevant (async, GUIs), which might be why it seems to be
| gaining traction in that area.
| Narew wrote:
| btw, fzf is written in go ^^
| matheusmoreira wrote:
| > We'll see in a few years when more "heavy shit" has
| been written/rewritten in rust.
|
| I'd really like to see that. Would be cool to see a
| completely new Linux user space written in Rust. Not
| necessarily a rewrite of existing software, new ideas
| would be great. I tested Linux system calls and they
| worked very well even though they needed experimental
| inline assembly functionality to work. With system call
| support, anything is possible.
| pjmlp wrote:
| Once upon a time there was a project to do a Linux
| distribution in Ada.
|
| Unfortunately it died a couple of years later.
| nibbleshifter wrote:
| Slowly there's more and more implementations emerging of
| Linux userland utils in Rust.
|
| It's taking a while.
| Rusky wrote:
| > a) it's not actually memory safe once you start doing
| heavy shit b) modern C++ is quite memory safe
|
| This just doesn't capture the problem that memory safety
| solves. A crashed program is not the worst-case scenario
| that it's trying to avoid. Even the most memory-safe
| language supports exiting early with an error message, or
| whatever.
|
| In terms of language semantics, there is an all-or-
| nothing line between memory safety and undefined
| behavior. A memory safe program does what it says,
| locally, step-by-step, according to the semantics of the
| language. When a program exhibits UB, those guarantees
| are lost.
|
| Of course, as you note, unsafe Rust also lets you violate
| memory safety, and in fact any memory safe language is at
| the mercy of its implementation and host. The reason
| people get evangelical about Rust's memory safety is one
| level higher: it offers a bridge _back_ to memory safety,
| such that unsafe code stands on the same footing as the
| core language. When either are bug-free, the compiler can
| ensure they are used correctly, using the same type
| system features for both.
|
| Modern C++ is certainly much less error-prone than the
| bad old days of manual `new` and `delete`, but it doesn't
| have an answer to this "unsafe encapsulation." To the
| contrary, modern C++ actually adds a bunch of _new_ ways
| to violate memory safety by misusing library APIs.
| Iterator invalidation, use-after-move, string_view and
| span and borrowed ranges, by-reference lambda and
| coroutine captures, etc.
|
| This all means that "serious systems software" in C++ has
| to approach memory safety via defensive copying or
| refcounting, copious use of sanitizers, and sandboxed
| sub-processes. Meanwhile, Rust programs can do things
| that would be unthinkable in a large C++ codebase,
| because the assumptions of both the language and unsafe
| code are encoded in the type system. (For example:
| https://manishearth.github.io/blog/2015/05/03/where-rust-
| rea...) It's a qualitatively different solution to the
| problem.
| jjnoakes wrote:
| > a crashed program is a crashed program whether I
| dereferenced a null pointer or was poking around in a
| slice with multi-byte Unicode characters in it
|
| They aren't the same thing though, that's the point.
|
| Dereferencing a NULL pointer isn't guaranteed to crash.
| In fact if you are writing through the pointer, you may
| even have a security issue on your hands (rce, etc).
|
| Safe Rust may have runtime errors that "crash" the
| program but this is a controlled, well-defined
| termination, and there is no way for the execution state
| itself to be corrupted like in C++.
| vvanders wrote:
| Yep, my favorite part of heap/stack corruption is not
| when it crashes immediately but rather when it rears its
| head 2-3 weeks/months later when some upstream call
| pattern or timing has changed.
|
| I've spend _weeks_ chasing down single instances of this
| on multiple projects. The nasty part is you have no
| predictability in if it 's going to be one that crashes
| immediately, silently writes garbage(hopefully not to
| disk!), is a latent lurking crash or security vuln.
|
| If you trash the stack then there's a good chance you
| lose the backtrace as well which can make a hard to debug
| issue become "find the needle in the haystack". I hope
| it's something that reproduces quickly and consistently
| because otherwise you're in for a ride.
| ncmncm wrote:
| It has been years since I spent any time chasing down
| memory usage errors.
|
| I recommend compiling with warnings turned on, and acting
| on them.
| ncmncm wrote:
| Rust is "cool", lately. But it _lacks basic features_
| that enable capturing important semantics in libraries.
|
| So the tradeoff is not relative safety against a little
| compile-time inconvenience. The tradeoff is against "no,
| you cannot code that thing at all, suck it".
|
| So almost all discussion of relative safety (which Rust
| advocates would like us to think is absolute) carefully
| sidesteps the point that there is a very great deal that
| cannot be expressed in Rust at all -- and not because
| expressing those things would have been at all unsafe.
| jiehong wrote:
| Java falls a bit in between Go and Rust on that example:
|
| Closures only allow variables that are effectively final, aka you
| can't reassign them (the compiler will stop you).
|
| But you could pass an object and update its internal state.
| qsort wrote:
| "final" in Java is kind of useless, really. A const mechanism
| like C++ has would go a long way and would be a perfect fit for
| the OO nature of the language.
| eganjs wrote:
| Final in Java means the value of a variable, property or
| parameter will not change after its initial assignment.
| Values in Java can be either a reference to an object or a
| primitive such as an integer, double or bool. It's definitely
| far from useless as it asserts that the reference or value
| you capture in a closure is not an old version that has been
| replaced, this approach is a consequence of Java disallowing
| arbitrary pointers. IMO Java's biggest mistake here is
| mutability by default, which Kotlin has learned from. If you
| understand why it is this way it makes a lot of sense and tbh
| I think it promotes better code. That said I would like to
| see more immutability in Java and with things like Record
| classes you can see Java is moving in the right direction.
| qsort wrote:
| I agree with that and I use final as much as possible. E.g.
| instance variables that don't need to change, I almost
| religiously declare them as "final" and initialize them in
| the constructor.
|
| What I mean is that what Java really "should" have is const
| like C++. A C++ function with a prototype of:
| int doSomething(std::vector<int> const &x)
|
| tells me much more than the equivalent Java:
| int doSomething(final List<Integer> x)
|
| Also C++ member functions can declare themselves as not
| modifying their "this" instance. E.g. there's no way to
| write this code in Java: int
| X::doSomething(std::vector<int> const &x) const {
| ... }
|
| which is an extremely powerful, compile-time checkable
| description of what we are doing.
|
| It's not that final is useless (probably wrong choice of
| words there), what it does is okay and it's correct to use
| it as much as possible, but it's a far cry from the static
| guarantees afforded by const-correct code.
|
| I also agree that the correct approach is immutability by
| default, but that ship has sailed, and it's also an
| orthogonal concern to what I'm saying here.
| kcartlidge wrote:
| Totally not the point of the article, and totally _subjective_ I
| know, but to me the thing that jumps out is how much more
| readable the Go code is than the others.
| deeptote wrote:
| This is the main reason that I use Go as my main language, and
| why many orgs are starting to adopt it: it's easy to read and
| jump into. I would argue, however, that it's very easy to
| create antipatterns and just general spaghetti code with Go. A
| language that's easy to be productive with != one that's also
| easy to maintain. Design and philosophy becomes very important
| with large codebases in the language.
|
| source: consultant, seen some truly heinous Go monoliths.
| bsaul wrote:
| There are definitely some ways to do bad designs in go, but i
| have the feeling it will be more immediately apparent what's
| wrong (or at least what part of the system needs rework). The
| reason being that there are no ways to obfsucate an awful
| design by wrapping it on mountains of generics programming
| and language sugar, making the whole thing a lot worse.
|
| It's only my gut feeling, but does that match your experience
| ?
| arriu wrote:
| I agree. However, my gut reaction was that the style of the go
| code was written differently than what I'd expect if asked to
| work off the rust version.
| agent281 wrote:
| Definitely subjective. I don't find some parts very readable.
| E.g., this line took my a minute to parse:
| append34 := func() func([]int) []int {
|
| If I was going to rank the readability I would say:
|
| 1. Rust function bodies 2. Go code 3. Rust function signatures
| 4. C++ code
|
| Which you could argue is me shifting the boundaries a bit, but
| sufficiently statically typed languages seem to develop two (or
| more) sublanguages. Global complexity definitely pushes Rust
| down peg.
| andai wrote:
| I don't write Go but it might have something to do with this:
|
| https://go.dev/blog/declaration-syntax
|
| Go's type syntax is unusual but supposedly much clearer when
| things get more involved.
| [deleted]
| hkalbasi wrote:
| Can you mention the difference that made Go code is more
| readable than C++ and Rust?
| wruza wrote:
| _comparaison_
|
| Probableur a typeaux?
| [deleted]
| rob74 wrote:
| Was this posted by a French speaker? Because in English it's
| "comparison", not "comparaison"...
| jeffbee wrote:
| There's a solid argument in here but it feels like there must be
| a better example. Can we think of a function that does something
| worth doing, in a way that programmers of all these languages
| would actually use, and that sets a subtle trap for C++
| programmers? When I read this article all I see is a useless
| function that contains a completely obvious trap which, yes, Rust
| prevents, but also just thinking at all would have prevented.
|
| Another small thing: the C++ in this article looks weird to C++
| programmers because it qualified vector with std, but does not
| qualify move.
___________________________________________________________________
(page generated 2022-07-30 23:01 UTC)