19 ms·
C++ still isn't cutting it
- sesuximo 6y agoAlternative title: mediocre C++ is arguably worse than mediocre rust. I’d agree with that? I think the power and danger of C++ is that it assumes the programmer knows best. Rust assumes the programmer is wrong if it looks dangerous. So.. yes?
- pizza234 6y agoTwo points should be considered. First, trivial: what's exactly mediocre in the Rust example? I don't think it's mediocre at all. Now, the second, which is significant: what constitutes a "mediocre" C++ program? The author is the engineer who built the Meson build system - his code is representing the output of an experienced engineer. This brings to the usual C/++ criticism: it makes it easy to make certain class of mistakes even for experienced programmers.
- golergka 6y ago> First, trivial: what's exactly mediocre in the Rust example? I don't think it's mediocre at all. A better way to phrase it would be "code produced by a middle-level engineer". It just so happens that in Rust a regular, adequate, middle-level engineer can easily write the best possible code because the language guides him to it, but in C++, a regular engineer still wouldn't know about some gotchas that would make his code inferior. It's unknown unknowns: Rust shows most of them to you, while C++ stays silent.
- helsinkiandrew 6y agoYes, there’s nothing wrong with C++ with very good programmers who know the language well and what they’re doing. Unfortunately not every programmer is or knows their limitations.
- orwin 6y agoI'd argue that even very good C++ programmers make mistakes that can be harmfull, and that those mistakes will be hard to catch by the average programmer who will deploy the new code. That's in my opinion why on a small to medium project where objects are not needed, it is better to use C than C++.
- zabzonk 6y ago> even very good C++ programmers make mistakes that can be harmfull Are you seriously saying that this isn't also true for C, (and indeed for all programming languages)? A challenge - try writing a program in C to read a text file containing lines of unspecified length, sort them, and output them in sorted order. Compare the C++ and C code and tell me which you think is the cleaner and safer.
- ratboy666 6y agoYes, very good programmers can make mistakes. To meet your challenge, think about the problem. How about this? file size + 1 => s allocate char buffer b[s] read file to b (all data must be in memory, or merge sort. if on linux and allocation worked, read may fail! oom will get us, or we bail on read failure) add final newline if missing count newlines => n allocate line pointers p[n] (if line pointers will not fit, we bail -- we could use offsets and fancy virtualization, if this is not a Z80) fill pointer array p sort => sorted[n] (if sorting fails, bail) output sorted lines Same for C and C++. UTF-8 is ok. Do you want more data than will fit into memory? Even then, C++ doesn't bring much to the table. Separating the sort algorithm? Yes C++ BEGINs to pull ahead by a bit. safe? yes. easy to write? yes. no fancy bits? yes. Transcribe to C C++ as you will. Java? won't be as pretty. Rust, Go? don't know. Javascript. Not even close. Since this is a "beginning programmer" problem, give a design that works for another language that makes sense to a beginning programmer.
- zabzonk 6y agoThat is not C or C++. And you cannot "transcribe" pseudo code like this into a real programming language.
- markus_zhang 6y agoJust curious can we try a C version?
- zabzonk 6y agoYou can try, but it would likely be at least 10x the length of the C++ (or rust) program.
- saagarjha 6y agoMaybe 5x, with most of that increase being a handrolled associative array.
- zabzonk 6y agoYou also need at least dynamic string of some sort. Actually, I would estimate 20x - I was being kind with 10x. But feel free to prove me wrong.
- tptacek 6y agoWhy do you need a dynamic string of any sort for this problem? (I think you estimated badly, for what it's worth.)
- saagarjha 6y agoYou'd need it to read words, unless you're using a libc function that does the allocation for you. (Not that you actually need a full string, realloc works just fine for the code I'm writing.)
- tptacek 6y agoYeah, I just used strdup and strsep, like a C programmer. You don't need a dynamic string to count words. :P
- 6y ago
- tines 6y agoIt seems like this is more a critique of the design of the standard library than the language itself. You could create a library that behaved similar to the Rust version, offering the same compile-time safety checks, in C++. I'm sure it would also be equally trivial to parallelize the C++ version with Boost or some other library (except that you wouldn't get the nice error about the unlocked hash map, so I have to hand it to Rust on that point). I think the author's problem is that C++ even allows you to do it the wrong way in the first place, but I'm sure that you could design an unsafe FS API with Rust as well (though it might be a little harder).
- arka2147483647 6y agoDo you truly feel that the standard library is not part of cpp? In general, i think a lot of valid criticism of cpp is dismissed with ”But you could do it correctly with X” Perhapse, but if everything is a special case, what does that say about cpp?
- beached_whale 6y agoThe article does seem to nitpick a C++ design and then highlight the same feature in Rust. The way getline failed there is not "accidental" but the design of fstreams/getline, so that "less-careful" programmers don't have those corners here. Thing is, all of the solutions to the issue here would be a real PITA if done as one function as both examples are. That is why example code often skips checks to allow the point of the article to be highlighted and not hide it. This is more true with filesystems related code as filesystems are not reliable. One example of how many corners filesytems have is the unit tests for sqlite, they are amazing.
- deleted 6y ago[deleted]
- imtringued 6y agoSure, you're right. In practice nobody actually does what you're saying though.
- deleted 6y ago[deleted]
- axegon_ 6y agoThough I've been a rust fanboy for quite a while (and I just switched jobs and since Thursday, I work in a company and more specifically team where rust is a primary language), I'm missing the point here. Happy days and all but... I don't think that C++ isn't cutting it here. I'll grossly oversimplify it but tl;dr you can achieve roughly the same results with C++ but it will take more effort. But that has more to do with the nature and the design of both languages(and their respective standard libraries) more than anything.
- sudeepj 6y agoFrom an ecosystem p.o.v Rust has a big advantage. The strictness is built into the compiler and the entire ecosystem is subjected to same high standards. One has to fix the errors. The same strictness in C++ can arguably be availed by using static linters. This has 3 problems: 1. Static linter errors are inherently optional to fix. One has to have some CI rules to block check-ins if linter flags something 2. Your project might follow best practices but not your dependencies. 3. Static linters are not foolproof (last I checked back 4-5 years)
- pjmlp 6y agoAnd npm style of libraries, which does very little to improve quality versus C++.
- devwastaken 6y agoSpecifically how does it make it worse than any other library package manager?
- pjmlp 6y agoHaving a thin standard library, which makes reaching out to third party libraries a must have in any project, many of each far from reaching 1.0, and not necessarly available in all deployment targets of the compiler. There is stuff in ISO C++ standard library that requires third party libraries in Rust, yet another thing to certify in security sensitive domains.
- higerordermap 6y agoWhy is this downvoted? Being politically incorrect to web developers?
- rvz 6y agoAs a Rust user, I'm afraid I disagree with these following points: > The strictness is built into the compiler and the entire ecosystem is subjected to same high standards. > 2. Your project might follow best practices but not your dependencies. The crates ecosystem is the weakest part of Rust and it is not always subjected to the 'same' high standards, despite the 'strictness' of the compiler. A simple command line app or library (even worse) can depend on hundreds of other third-party libraries and cargo reminds others of the same ills of npm to some extent as developers throw in unnecessary libraries in your project. Point (2) can be still said about Rust given that the developer can still abuse unsafe{} or still have dangerous .unwraps() written by the developer. Just look at the immense scrutiny that actix-web was under because of this.
- acqq 6y agoI don't think that the Rust version is as different as the author seems to believe. For example: the author writes "What happens if the file has been replaced by, e.g., a pipe or other not regular_file?" But it seems to me that the same problem -- replacing the file which is regular during the directory walk with the pipe exactly before the said pipe is opened -- can exist in Rust version too. It doesn't matter how the syntax looks like, I suspect the equivalent system calls are the same, Rust will also walk the directory at T1 obtaining metainformation, at T2 open the file, even if it is at that moment actually a pipe, and then at T3 check if the metainformation obtained at T1 is a regular file ("file" in the source?), which could still be that? That says to me that the both version can suffer from the same bug. Luckily it's something that's not going to happen too often in practice, knowing how typical programs don't use the same file name for a pipe and a file in rapid succession.
- Filligree 6y agoThe Rust version in fact doesn't have that problem. It opens the file, then checks the type of the open file. You could do the same in C++, but in Rust it's the default. So...
- acqq 6y agoWell, I don't see that the "metadata" are actually obtained from the opened file handle in the Rust version? Metadata request must happen at the different time point than the open of the file and the API as far as I see works on the path, not on the handle: https://doc.rust-lang.org/std/fs/fn.metadata.html https://doc.rust-lang.org/std/fs/fn.metadata.html which says to me that the Rust version can manifest the bug of the same kind -- there are different time points in which different independent information is checked, and that all the calls correspond to the same opened file handle just can't be assured? As far as I understand only checking the properties of the opened file via the same file handle is safe for the described kind of bugs, nothing else.
- steveklabnik 6y agoYou can get it from a File too: https://doc.rust-lang.org/std/fs/struct.File.html#method.metadata https://doc.rust-lang.org/std/fs/struct.File.html#method.met... (This doesn’t invalidate your point, of course, just saying you can do this.)
- zabzonk 6y agoQuality of a specific program (that is not a good C++ program) is hardly a reflection on the quality of a language.
- mrkeen 6y agoIt is after the compiler has accepted it with no warnings or errors.
- billconan 6y agoI don't understand this article. I think there is no language level feature in Rust to prevent TTCTTOU. TTCTTOU can happen in both c++ and rust. for example: let f = File::open("username.txt"); /*what if the file is deleted here?*/ let mut f = match f { Ok(file) => /*what if the file is deleted here?*/ file, Err(e) => return Err(e), }; /*what if the file is deleted here*/ I mean even if you write it like: let f = File::open("username.txt")?; it looks atomic, it is not. the c++ example, on the other hand, can do a filtering too, using: http://www.cplusplus.com/reference/fstream/ifstream/is_open/ http://www.cplusplus.com/reference/fstream/ifstream/is_open/ I mean Rust forces you to do error handling, which is nice. But that's not enough for preventing TTCTTOU. I don't think the first example is a strong argument.
- sudeepj 6y agoI am not sure what TTCTTOU is, but what you are saying needs transaction like semantics in filesystems. So its more of a property of an FS instead of a language. All the common FS (in linux .. ext* xfs) do not have that these.
- merrywhether 6y ago“time to check to time to use” As you intuited, a problem that arises in environments that lack transactions/locks/etc
- Iwan-Zotow 6y ago> All the common FS (in linux .. ext* xfs) do not have that these You know that files are nameless on Unix, right?
- sudeepj 6y ago> do not have that these I was referring to the transaction semantics
- Iwan-Zotow 6y ago
- brundolf 6y agoTo summarize: 1) Rust's standard idioms (in this case, Results and Iterators) encourage less bug-prone or ambiguous APIs (standard and otherwise) 2) Rust's ecosystem makes it easy to find and integrate solutions not offered by the standard API 3) And then finally, Rust's borrow checker prevented a threading issue Others are rightly pointing out that only #3 is fundamentally unique to Rust. All of these other things could be done for C++. But, importantly, they haven't. This raises an interesting dimension to the comparison which is that many of Rust's advantages don't really come down to its unique traits (heh) but to the simple fact that it's "C++ without baggage". It had a fresh start when it came to establishing standard idioms for everyone to use, providing cross-platform tooling for everyone to use, providing centralized package management for everyone to use, etc. If you introduced to C++ a Result type, or a package manager (I assume people have done this already), most C++ developers wouldn't end up using them. Most libraries wouldn't be using the same vocabulary. It would be arduous to even move the standard library over, because it would be a breaking change. Much of the tooling out there would probably never get specialized support. I don't think these network effects and cultural forces get talked about enough. Sure, this stuff is "just a library", and C++ has libraries. But the culture and the baggage and the stakeholders around a language have a huge impact on what ends up being practical to do with it, independent from what's technically possible.
- zabzonk 6y ago> If you introduced to C++ a Result type I don't know too much about the details of rust, but: std::optional? Whether people will use it is of course up to them, but my feeling is that they will.
- steveklabnik 6y agoNot the same, Optional is like Option in Rust, it’s about existence, not success or failure. I believe std::expected is the correct answer but it’s unclear to me if it actually made it into the standard yet or not. I thought it had but can only find proposals...
- 6y ago
- superjan 6y agoThis article suggests it refutes the linked article. That was more about the developer experience than about safety. That rust is better for safety is not news.
- jeffbee 6y agoVery unfamiliar with Rust, very familiar with C++. As a reader of this Rust program, how can I figure out what's happening? I don't see anything in GlobWalker documentation that even slightly implies that the filter_map calls that follow are appropriate. Also, why the first filter_map(Result::Ok)? Implies the iterator emits Result? Hard to follow, to be honest.
- burntsushi 6y agoIMO, globwalk is inappropriate to use here. It's bringing in a ton of unnecessary dependencies for such a simple task. Just using walkdir directly (which globwalk depends on) and filtering on the file extension would be just nearly as simple and use oodles less code. This is IMO a good example of poor choices that an "npm-like ecosystem" can encourage. Obviously there are deep and fundamental trade offs here, but other than watching crates compile, you're almost completely removed from just how much code you're bringing in relative to what is really necessary.
- dgacmu 6y agoThanks - I appreciate this feedback. I've updated the post with a modified version at the end that directly uses walkdir (and I agree with you). To the GP, the place it shows up in the docs is mostly in its example: if let Ok(img) = img <-- this is a sign they're unpacking a Result type.
- burntsushi 6y agoAwesome! And globwalk is a great crate, when you really do need high performance glob walking. I think there is room to improve it so that it doesn't use so much code, but it's a bit of work to do that. (I see this as largely my failure, as the 'ignore' crate is primarily to blame here.)
- deleted 6y ago[deleted]
- Measter 6y agoSo, this[0] is the doc page for the `GlobWalker` type. One of the traits it implements, and the only one relevant here, is the `Iterator` trait from the standard library[1]. That's how you'd know that the `filter_map` function is appropriate. The implementation there also tells you that the `Item` type of the iterator is a `Result<DirEntry, WalkError>`, presumably because any of the file system reads can fail. The `WalkError` type is from the globwalk library. Going back to `filter_map`[2] that function takes a function or closure that maps from `Item` to the `Option` enum, and then just discards anything that is the `None` variant of the enum, filtering them out. `Result::ok` is a function on `Result` that converts the `Result` to an `Option`, converting `Err` to a `None`. So this is discarding the errors without reporting them. As someone who is familiar with Rust, this wasn't particularly hard to follow, but that comes down to familiarity with these adaptor functions and how they can be plugged into each other. [0] https://docs.rs/globwalk/0.8.0/globwalk/struct.GlobWalker.html https://docs.rs/globwalk/0.8.0/globwalk/struct.GlobWalker.ht... [1] https://doc.rust-lang.org/stable/std/iter/trait.Iterator.html https://doc.rust-lang.org/stable/std/iter/trait.Iterator.htm... [2] https://doc.rust-lang.org/stable/std/iter/trait.Iterator.html#method.filter_map https://doc.rust-lang.org/stable/std/iter/trait.Iterator.htm...
- x87678r 6y agoAs always new languages have a huge benefit of having new code bases. If you take a C++ job now, 95% of the time it will be a code base that is at least 10, and likely 20-30 years old. If you start a Rust job it will be modern.
- jasonhansel 6y agoAnother, perhaps more important difference: the Rust version converts to lowercase in a Unicode-safe way, whereas the C++ version really can't work for multibyte character encodings.
- account42 6y agoThere is no such thing as converting to lowercase in a Unicode-safe way. Or rather, there are many ways.
- janoc 6y agoAnd yet another "C++ sucks, Rust is better" thread. Why are these C++ bashing threads practically always about Rust? I have nothing against the language but I do feel the Rust devs have some sort of inferiority complex and a compulsive need to convince everyone about the superiority of their tool. Guys, these endless pissing contests are not really productive.
- sfkdjf9j3j 6y agoIt's an article about Rust, why wouldn't people be talking about Rust in the comments?
- saagarjha 6y agoI'm sure the implicit question was "why are these articles always about Rust".
- the_only_law 6y agoBecause alternatives are either not mature or too old and not C-like enough to be cool ig
- blub 6y agoAnother plausible explanation is that Rust job numbers continue to be rather thin, which gives Rust fans on average more free time to write blog posts and open-source libraries. :)
- FpUser 6y ago>"What happens if the file has been deleted between the directory listing and the open?" Did author even know that one can check stream state or have it throw exception. It is a very poor attempt at pissing contest.
- spiffytech 6y agoI think it's because Rust was started by a high-profile, respected company specifically because they felt C++ was inadequate for their use case, and they had the expertise to be authoritative about that inadequacy. That means Rust is a technologically natural counterpoint to C++ because it was literally designed to be a C++ replacement, and coming from a high-profile company means its entire target audience found out about it very quickly, so any discussion relevant to Rust can find plenty of participants. The rest is just due to all the usual reasons programmers bicker about things, and we've seen this language bickering again and again before, it's just a different language's turn in the spotlight. I'd love to see these discussions include a more coherent analysis of when a language is appropriate, instead of assuming everyone should always make the same choice. If I'm starting greenfield, when should I pick C++ over Rust? If I'm looking at C++ alternatives, how would I choose between Rust and Golang? Is D ever a better choice? When? Why do we act like you can only pick one, when IPC and FFI and microservices mean you can split the difference if a language is only best for a portion of your project?
- toxik 6y agoThe author talks a big game about safety and security, but fails to mention the ENORMOUS attack surface brought by the fact that the Rust variant uses third party code, written and hosted by <An Ostensibly Nice Person Who Never Gets Hacked>.
- petr_tik 6y agoSecurity First of all, the proliferation of header-only libraries that people vendor into their Cpp projects has the same attack surface. Additionally, there is less tooling to help you track vulnerabilities in Cpp dependencies and upgrade when fixes come out. If you do rely on external cpp deps, then you must have a code review process before those deps are whitelisted for use. Now, you can extend this code review process to rust crates. Run a security review of a crate after which that crate is pushed to an internal registry. Every external crate upgrade can go through the same security review process until which point developers will build with the old version present in the internal registry. In the context of a large enough organisation, an increasing number of crates will become internal, thus solving the trust/responsibility issues. by no means, do i suggest that crates is unhackable. That's why I want to raise awareness of already existing infrastructure and procedures to vet code before including in your systems. Safety Before we get to ownership and some thread-safety, how do you explain the fact that integer addition and number conversion are safe operations in Rust, which throw/panic when they fail instead of the default Cpp behaviour, which _silently_ corrupts data?
- saagarjha 6y agoThe C++ article did not use any header-only libraries, though, it just used the standard library, unlike the post about Rust. And Rust's behavior on overflow is "safe" for a certain definition of safe–in release builds by default it will wrap, which is often undesirable.
- petr_tik 6y agoI assumed "enormous attack surface" was referring to real systems, not toy examples. AFAIK, even C++ needs more than the std library to solve business problems in the domains I am familiar with. There are many ways to manage dependencies including vendoring in header-only libraries, where my point about the lack of tooling and clunky UX still stands. I just want to use tools that help me get stuff done. C++ has had a 30-year head start, why can they not see the overall value-add of a default build and test tool and a format for package declaration and management. All integer types have "checked_" arithmetic operations, which return None in case of overflow. https://doc.rust-lang.org/std/?search=checked_ https://doc.rust-lang.org/std/?search=checked_
- dang 6y agoThe post that this is responding to was discussed here: https://news.ycombinator.com/item?id=24805717 https://news.ycombinator.com/item?id=24805717
- Jeaye 6y agoC++'s more recent changes, in the past decade, have made it easier to write. However, adding more to C++ is never going to make it simpler. C++ still carries all of the baggage of C and all of the baggage of the older C++. None of these can be removed without breaking millions of programs, but all of these things can still bite C++ devs and will still exist in older C++ code. For example, undefined behavior by default, value categories (rvalues, lvalues, etc), exceptions in destructors, SFINAE, and so much more. Even if C++ were to end up making its syntax closer and closer to Python, which it seems to be doing, beneath the sugar is still a haggard mound of a ridiculously complex and unsafe language. The only way to change that is to start fresh, as Rust did. Rust is complex, indeed, but it removes a whole lot of the safety concerns and legacy crud. Presumably, 20 years from now something else will be created which improves upon Rust in such profound ways and we can all talk about how complex Rust is and how its idioms aren't as <something> as they could be. Maybe we'll be using machine learning to write and compile code for us and Rust will be shat on for being so complex and manual. But, for now, even with its complexity, it should be a breath of fresh air for anyone working on non-trivial, multi-threaded C++ applications. There will be those who don't dig it, just as there were those who have stayed with C all this time. But how else would we keep the vulnerability finders busy? Everyone needs to eat.
- Daishiman 6y agoI think it would be totally possible for C++ to adopt and "epochs" model where you mark your units of compilation to support specific language features and that would make life tremendously easy. You could even mark epochs where legacy features get marked as deprecation warnings, etc.
- cb321 6y agoFWIW, the Nim version is (no deps besides stdlib): import os, strutils, tables, heapqueue iterator topByVal*[K, V](c: Table[K, V], n = 10, min = V.low): (K, V) = var q = initHeapQueue[(V, K)]() for key, val in c: if val >= min: let elem = (val, key) if q.len < n: q.push(elem) elif elem > q[0]: discard q.replace(elem) var y: (K, V) # yielded tuple while q.len > 0: # q now has top n entries let r = q.pop y[0] = r[1] y[1] = r[0] yield y # yield in ASCENDING order template maybeCount(count, word: untyped) = if word.len > 1: count.mgetOrPut(word, 0).inc proc main() = var count = initTable[string, uint]() for path in ".".walkDirRec: if not path.endsWith(".txt"): continue for line in path.lines: var word = "" for c in line.toLower: if c in {'a'..'z'}: word.add c else: count.maybeCount word if word.len > 0: word.setLen 0 count.maybeCount word # line ends in a word for word, cnt in count.topByVal: echo cnt, " ", word main() Doing a gcc-10 profile-guided-optimization build on this, I get the min time of 5 trials running on Charles Dickens' Tale Of Two Cities { opening paragraph is very optimization claims relevant! :-) } available here: http://www.textfiles.com/etext/AUTHORS/DICKENS/dickens-tale-126.txt http://www.textfiles.com/etext/AUTHORS/DICKENS/dickens-tale-... 7.3 ms EDIT- tptacek's C version (w/bug-fix & new tokenization) 15.8 ms Above Nim 1.4 PGO --gc:arc 24.8 ms Above Nim 1.4 --gc:arc 27.3 ms Original C++ PGO 30.2 ms Original C++ -O3 only 42.6 ms dga's Rust with rustc-1.47.0 build --release This is on a i7-6700k. So, Skylake core and probably more importantly 8 MiB L3. So, also FWIW, I could not reproduce the Rust/C++ timing ratios with the above mentioned input file. I would also point out that not only is globwalk unnecessary as @burntsushi points out, neither are regexes nor even sorting. A heap is a better way to do "top N", but you may need to reverse the answer if you care. I mean, maybe the point of the original C++ is to also bench/exhibit use of regex engines and part of the Rust point is utf8 or some such, but to me that seems more like a distraction. Anyway, maybe my rust build environment is screwy. Hence, my inclusion of an exact input .txt file for others to try out if they care. I had to add some extern crates and [dependencies] of anyhow, lazysort, regex, globwalk. I've never done a PGO build with rust, either. So, that may help.
- dgacmu 6y agoInteresting. I've used this same file for a test on: - I7-7920HQ, MacOS Rust version: 90ms (rustc 1.47.0 (18bf6b4f0 2020-10-07)) C++ version: 340ms (Apple clang version 12.0.0 (clang-1200.0.32.2)) - Xeon(R) Gold 6130 (skylake, Ubuntu 20.04) Rust: 76ms (rustc-1.47.0) C++: 60ms (gcc version 9.3.0 (Ubuntu 9.3.0-10ubuntu2)), -O3, no PGO Seems quite CPU and compiler-dependent. Odd that the results on MacOS were so horrible for C++. And thanks - I've updated the post to include this more-reproducible benchmark, and included both macOS and linux results. One note: I modified the single threaded one to use walkdir, but that shouldn't affect time in a major way. The macos timings were about the same. And yes, I agree about the top N part. I deliberately tried to remain "algorithm-compatible" with the C++ example; there are lots of tricks to use to speed this up more. Getting rid of the line-at-a-time processing would be a good start, for example - it results in an unnecessary double-scan of the input.
- fizixer 6y agoTitle needs to be fixed: C++ still isn't cutting it for a Rust fanboy