What it feels like when Rust saves your bacon
smallcultfollowing.com
smallcultfollowing.com
I must say - as someone who doesn't use rust - I haven't had this type of issue in years, and when I did it wasn't hard to debug. You get corrupted data, set data breakpoint and in the provided example you will see it being modified by unrelated operations on stack. From there there is only one conclusion. Authors reactions seems to be a bit exaggerated.
Sure, but the development pattern that get used for larger applications typically don't benefit from Rust's additional safety; for one you're going to be using a garbage collected language.
I wish it were true, but promise you it is not. As a counterpoint, I point to most "AAA" gamedev and OS development.
In gamedev it's even a bit flipped: The smaller indie gamedevs can pay the GC hit for Unity's C#, web JS, actionscript flash back in the day, etc. - not much working data, not much garbage. Larger scale titles start missing vsync and having horrible stuttering when GCs are thrown into the mix too brazenly - they're still used on smaller scales (embedding browser tech for UI, limited scope scripting, etc.) but they have a lot of native, non-GCed code.
Large network-exposed app? Individual memory corruption heisenbugs have taken me weeks to track down (and weeks before that for QA to create a reliable repro for) - a needle in a huge haystack. They often predate my employment - having lurked semi-silently for who knows how long causing who knows how many unreported crashes. When release dates slip because of bug backlogs filled with memory safety related crash bugs, when ~70% of many vendor CVE reports are down to memory safety issues [1][2][3], and when you personally have to deal with the fallout of all that: shuger's point completely and utterly evaporates.
[1] https://msrc-blog.microsoft.com/2019/07/18/we-need-a-safer-s...
[2] https://www.chromium.org/Home/chromium-security/memory-safet...
Languages are tools and some tools are actually better-built than others. If we can claim that a language like Brainfuck makes writing clear code extremely difficult, and Python or Rust make writing clear code easier, we've already established that there's a spectrum for language in expressiveness and clarity.
Eliminating entire classes of bugs makes for better understanding.
int a = 0;
if (findindex(mylist, myvalue, &a)) {
// dostuff with a
}
Here, findindex returns false if it can’t find the value. The problem is that there’s nothing forcing you to use the if, you can just forget it and you’ll be left with incorrect code. In Rust, this type of error is impossible to make by accident, because the findindex function would return an Option, and you have to explicitly handle both cases (or explicitly say you don’t care about one of the cases).Things like that, along with the lifetime system, make it easier to write good code. It’s like saying that it’s possible to destroy your foot with a shotgun and with a pencil – it’s possible, but it’s a lot easier do to by accident with the shotgun.
You can get a long way towards safety without learning Rust. It's those rare cases that will get you.
It's a trade-off; take the time to learn the language and deliver later, or just use what you already have to deliver a product now.[1]
[1] During a Rust discussion some years back, when I was at a different company, on a specialised and large-ish product written in C++03.
I went through about 3 years of tickets (limited to only the bugs reported). No open ticket was older than a few weeks. Out of maybe 1000 bugs, only a single one was something Rust would have prevented. I would think that most mature products will have similar stats, so the trade-off is not as obvious as it looks to be on the surface.
Deliverables matter.
[1] I say far less likely because obviously it's possible with unsafe Rust, but I've never had one happen, seen one happen in real code, or been affected by part of a dependency tree having one.
If you have 1000s of bug reports, of which 5 are CVEs, and then have 3 of those 5 be preventable, most dev teams are still going to consider the cost/benefit of going through the pain of developing a long-term product in Rust, or of switching to Rust altogether.
Those 5 are just the ones you know about...
It's pointless making a cost/benefit analysis on things that probably don't exist.
No.
This statement is meaningless without any insight on how bugs were created. If the bug reports exclusively dealt with "happy-path" or "business logic QA", then of course you won't see any CVEs. Did the use of fuzzers or address sanitizers create bug reports? Were these tools even used? If not, the claim that only one of 1000 bugs were memory safety issues isn't credible; you weren't looking for them so of course you didn't find them.
I think it says a lot when almost every C++ developer claims to have a higher quality code base compared to say Linux or Chromium when it comes to memory safety errors.
Rust lowers your mental load. You spend more time being creative and way less time debugging "obvious" (or not) mechanical problems (reference not-on-stack-anymore variables, use-after-free, concurrent write access and all kind of compiler undefined behavior). That why garbage collected languages are so successful (they let you concentrate on the business logic) and for the first time it's available in a system language.
Everything that can be done by your computer should be done by your computer. You should leave your precious brain cells available for the important stuff.
The issues that Rust is supposed to help with are simply not what we spent time on. All the bugs reported are pretty much exclusively root caused to "business logic".
From recent time I can recall only one that was a programming mistake and not architecture/business logic related. It was a missing break in a switch that already had some fallthroughs so it didn't look incorrect at a glance.
I do understand what Rust is supposed to provide but in practice it's simply an extremely minor source of bugs.
Driver should be even more prone to programming bugs because most of it is about manipulating data in raw "untyped" memory.
Finding and fixing issues in C++ code can be easy with the available tools, getting people to use them on the other hand can can be like talking to a wall.
We have a sister product written in a dynamic language, and sometimes we have identical functionality.
I've noticed that when a change is discussed, the c++ gang has architectured themselves into the current solution and therefore have a much harder time making changes.
So for that reason I think it's easy to overlook these complexities when you're working in c++ alone; they feel natural and are just part of how you work. You forget that a lot of this architecturing just isn't necessary in a lot of other languages.
That's also part of why I enjoy returning to c++, the people involved know how to structure code and create clean architecture.
That said, sometimes c++ does get in the way. Creating trees or graphs can be cumbersome, and IMO it's very biased towards virtual methods to solve polymorphism.
Extending lifetimes by pooling or similar is also quite common, and is in my eyes sometimes overdoing it. If you for instance use Rust, you can be a lot more confident that the compiler catches these issues, and be more conservative and efficient in the solution.
This is a function of the type of software you write. There are many large C++ code bases that rely on various types of static polymorphism almost exclusively, rarely having a use case for virtual methods or dynamic polymorphism. There is a similar story with inheritance versus composition; some types of code bases naturally gravitate toward one or the other.
The nice thing about C++ is that it as amenable to any of these models should it benefit the application.
So while it's there, I would say that the oo virtual method style is much better supported, although storage for those usually requires some type heap allocation.
I wasn't aware of this. Maybe I'm just out of the loop. Do you know where one can I learn more about this? I'm desperately trying to reduce the number of virtual calls in our codebase, but I'm hitting the aforementioned problems.
This has traditionally been managed with CRTP, tagged unions, etc with some scaffolding to make it convenient and compliant with strict aliasing rules. Ideally, almost all of the dynamic polymorphism is pulled up to the level of the page types, an opaque blob of I/O friendly complex data structure, minimizing the amount you have to do. It is also important to note that JIT-ing has replaced many of the use cases for dynamic dispatch e.g. adding user-defined schemas at runtime.
None of which may apply to your use case. Some things inherently require an unfortunate amount of dynamic dispatch.
Wouldn't Rust have the same problem with this (if not worse)?
Is it? These days I'd expect C++ to be very biased towards using templates for polymorphism. After all, templates are a thing that C++ provides with functionality that other languages often lack, whereas in the field of virtual methods/"dynamic dispatch OOP" C++ severely lags behind other languages. Choosing between two features of a language and using exactly the one that is worse in comparison to your competition feels wrong to me.
On the other hand, fuzzing large c++ programs will routinely uncover memory safety issues in practically any large codebase that hasn't been absolutely beaten to death by fuzzers already.
The issues are not usually so much "I returned this thing on the stack" they tend to be things like "this (very unexpected) sequence of api calls will result in a UAF in this deeply nested data structure over here on the heap".
Everyone claims this, probably because business logic bugs are more memorable. But I've never seen it match the real statistics. According to the best published data, null alone is something like 30-70% of bugs, you just don't remember them because they're uninteresting.
https://old.reddit.com/r/rust/comments/78bowa/hey_this_is_ky...
Every org's use cases are different, how do you get (let alone compare) data frm different orgs, who really counts their bugs anyway (and those that do at scale and in detail are probably doing suffering from some form of myopic management disorder or another), etc.
All you can do is ask people their gut take based on their particular experience. For systems engineers, a lot of bugs are due to memory safety. For more consumer-oriented startups (or in most any bigcorp), yeah, it's "business logic" (or people's inability / unwillingness to communicate), etc.
"We found that 70 percent of our bugs could have been prevented by moving to TypeScript", yeah sure.
1. High quality teams like the Linux kernel team and PostgreSQL, do periodically have serious security bugs that are things Rust would have caught.
2. I see sometimes C++ instructors making the same claims you do and then spending half of a lesson tracking down a memory mistake, (Casey Muratori) or in the same month a tweet from a game engine developer saying they don't really see the value of garbage collection and then tweeting about how they spent 48 hours tracking down a memory mistake. (of course, for a game engine that is what you have to do sometimes!)
There are however valid questions, like if Rust slows down your development say, 5%, would you get more net safety from spending 5% more time testing/fuzzing c++ code instead? etc.
The specific exercise I have in mind is a lockless thread queue. < 20 lines in C .. ~200 lines in Rust.
Other projects will really get into domains where you have to work hard to satisfy the borrow checker and it can slow you down a lot. In a real application you won't be writing lockless thread queues for a big % of the time. But then for a real application the compile times will start to weigh on you more. (Though, C++ does not always compile fast either unless some care is taken to be sure it does)
Those take time to run too and should absolutely be added to the compile time metrics when comparing against "cargo build".
I don't run a linter because I hate them, and my metaprogramming passes do a bit of extra static checking clang doesn't. I occationally run extra static tools (valgrind et. al) but they're painfully slow and very rarely catch anything.
Do they have an equivalent API? Rust does have a hard time explaining very, very low level stuff that you'd have to do unsafe and various magicks. But once you get the unsafe details right, the consumer API for them tends to be extremely rigid and fool proof.
A little odd for a supposed system langauge
Also, things like queues tend to be implemented in either the stdlib or a very popular library. So they are very well tested and likely to be widely reviewed.
I write code much faster in Rust than in C++. Part of it is thanks to the type system – fewer opportunities for dev errors means that I can produce code that is more concise, spends less time handling runtime errors because I can have static guarantees that they have already been handled somewhere, etc. Part of it is #[derive(...)], great documentation, Cargo and other QoL components of Rust.
Why is this your exercise you have in mind though? This is such a bad argument. Like, yes, if you work at doublylinkedlist.com where your entire job is writing a new linked list implementation every day of the week, Rust might be a bad choice. But that's not what any kind of commercial enterprise actually looks like. If I saw you writing a lockless thread queue at work, I'd tell you to stop wasting time.
That was an example I could point to where the code size difference between Rust and C or C++ is roughly 10x. I mentioned it to corroborate the claim that sometimes writing Rust is very verbose, on the order of ten times more.
My point was not that average commercial codebases are largely dominated my these types of structures. My point was that my experience with Rust has been that the development slowdown is much more than 5%, as the OP suggested.
>> If I saw you writing a lockless thread queue at work, I'd tell you to stop wasting time.
And that's one reason we don't work together ;)
I think a similarly valid question is how often you resorted to dynamic allocations just to get the borrow checker off your back. If your Rust version uses 5% more dynamic memory (with the corresponding performance and memory footprint penalty), is it perhaps worth staying with C/C++ and spending more development time on testing/fuzzing?
Is it harder to fuzz Rust? Honest question, because fuzzing is something I occasionally read about but am not practiced in.
Here's a Rust fuzzing story from yesterday: https://hacks.mozilla.org/2022/06/fuzzing-rust-minidump-for-...
It claims that Rust is particularly suitable for it because integer overflow panics in debug builds (and out of bounds indexing always panics), which sounds reasonable.
This is so much better than the outcome would have been in any C or C++ project despite the many protestations of "just follow modern best practices" adherents. The author of minidump is no novice, is well versed in best practices in multiple languages including C++, was sure the code was solid, and still got spanked hard by the fuzzer. Denial of service outcomes aren't ideal, but they were likely fewer in number and are unambiguously better than security vulnerabilities.
The effect might be positive or negative depending on the circumstances.
A lot of the rust performance talking points just aren't true. Rust is slower than C and C++, not by much but you can't get a true believe to even recognized this. Rust has turned into a religion.
Rust also disallows some things that you can do fine on C++ if you know our architecture because thing won't work on some machine 20 years ago (eg, it has more strict alignment requirements than any machine a consumer can see).
Throughput may only be 5% or more slower, but latency issues for rust is a much bigger issue. The devs I've talked to don't even try to pretend they have a good latency profile.
Safe rust would have caught. If you had to drop into unsafe to do what they did, the serious security bugs would still have happened.
By contrast and comparing apples to apples, your entire C or C++ codebase is the equivalent of one giant unsafe block. You can bring in 3rd party tools to perform some of the static analysis the Rust compiler does for you, but call it for what it is.
By default, Rust is safe while C & C++ are not.
Which very notably does NOT use C++.
Neither of those code bases are C++, which significantly dilutes your point. A major benefit of modern C++ is that it is much safer than the language Linux and PostgreSQL are written in.
There's a reason Mozilla has decided that all parser code should be moved to Rust ASAP.
There could be plenty of bugs in your codebase but just because you don't spend time on them doesn't mean they don't exist. Hundreds of millions of people used OpenSSL everyday for 2 years after the Heartbleed bug was introduced. It didn't cause any obviously broken code until someone exploited it to read credit card numbers off of a remote server.
https://godbolt.org/z/1MfErrYd8
...in more simple cases it's also a regular warning:
I would say it isn't even the debugging. As many of the C++ programmers here have said as you get good at C++ this becomes something that you are vigilant for and it rarely actually gets written. But just the lack of even needing to think about it is a huge load off my mind. When I used to write C++ I never really realized how vigilant I was. Every time I added code into the middle of a function I had to double check all of the lifetimes, every time I shorted the lifetime of a variable I had to search for it to the end of the function. Just not having to worry about this much really frees your mind for thinking about other things.
For me it was what lifetimes and types to use to make my program work. Just as it was in C++, but with a static verification step at the end which most of the time got in my way.
Rust makes lots of sense in high-churn projects or projects which have very high security requirements (like browsers). Otherwise I’d think carefully about using it.
Rust is even worse than C++ because it highly encourages encoding logic in types and the community loves doing that.
You seem to have almost come to the same conclusion yourself, but then mistakenly assume that the same kind of productivity of a GC language is available in a system language. Nope. Although at least in C++ one can just say “fuck it” during the “creative” prototyping phase, copy most things and still have decent syntax and performance. In Rust you’d have to pepper everything with clone, boxes and (a)rc, so you'd have another mess.
It doesn't take many of these to form a coping mechanism that prevents this from happening again, even if it's at great cost. This is also the genesis of many unwinnable arguments that drag on forever.
It also depends on what you're writing. If you're writing cryptographic routines, or protocol handshaking, the tradeoffs are heavily weighted in Rust's favor.
/s
That's provided that you can even reproduce the issue well, especially in an instrumented build which might be way slower than the non instrumented one. Often you get bug reports like "crash after one hour of usage" where basically every feature of the app has been heavily used by multiple users. Rust applications might still crash but they crash safely, which means your error messages are more meaningful.
That's the root cause, but it's not the interesting bit.
The code in the article comes from a production compiler. And normally, the AST (abstract syntax tree) is a single data structure output by the parser. Ownership is simple: the entire AST has the same lifetime, and it's managed by a caller. This should be easy, right?
But it turned out that there was a piece of code that sometimes "synthesized" extra, temporary AST nodes. And these nodes had a shorter lifetime than the rest of the AST.
These are vicious bugs. You have some long-standing convention about how things work, but one little piece of code makes an exception (often for excellent reasons). Then another module decides to make an aggressive optimization that relies on the original assumption. But that assumption is now true only 99% of the time.
It's a communication failure, and it might take years to actually turn into a bug. And that bug may manifest as extremely rare memory corruption that shows up in automated crash reports.
Running down this kind of phantom memory corruption is one of the most frustrating things I've ever done. It often involved spending weeks staring at minidumps, looking for interesting patterns in crashes. There's that horrible moment when you realize that 20% of your crashes occur within a thousand instructions after a particular font-rendering function reports an error, accidentally corrupting the exception-unwinding machinery.
And sure, I get it. Maybe your team is simply good enough that nobody ever makes a mistake like this. But if so, they're exceptional. I've worked on amazing teams that still get bitten by subtle miscommunications and misunderstandings.
The author isn't just telling you that Rust is awesome because it tells you something, he's acknowledging the frustration in learning how to listen to the compiler.
It's kind of like Jerry Pournelle describing the ups and downs of USB by documenting an epic journey that all started with trying to scan some handwritten notes for his next novel using a Canon scanner he borrowed from Alex that he's just now getting around to reviewing, because the pins of the parallel port are too bent to use the old Epson. Okay, so maybe it was a little overcomplicated.
We saw the data corruption, and we knew it was a reference issue, but it took quite a bit of effort to track down. The cause was confusion around string_view and string&, with different behavior when you pass each to a new thread.
Rust would have caught this much earlier and saved 3 days work.
Rust is a breath of fresh air in comparison. Worrying about memory safety isn't even a concern for the vast majority of commits that don't touch modules with unsafe code. All assumptions made about the lifetimes of references are made explicit in the code, and checked by the compiler. On rust projects I find I have much more mental energy to use against other aspects of the problem.
I skipped all the code and only read the text. I got to the point where it mentions that the language prevented them from storing a pointer to a stack object in a heap object, and that's really great, but boy oh boy, the code presented, and the error messages, where very difficult for me to grok even after the first few times...
It seems to me that the more sophisticated the safety a language provides is the more complex its error messages/code are. It would be so nice if we could solve both problems at the same time...
Reason being that you have to unlearn your paradigm before you’re able to learn the new paradigm; and it’s made slightly worse by giving the appearance of similarity (since certain concepts map directly, like flow control and loops).
Once they realize that the tooling is actually working _with_ them, not _against_ them, they start to pick things up much quicker.
Without going much further, to me it's the syntax. I can write some Rust lines of code, and I appreciate the compiler output to correct my syntax. But it's really difficult to me to read code written by others. I can't open a random file of a Rust project and have a minimal clue of what's going on
After years of C and some C++, I have a parser implanted in my cerebellum, and it's hard to adapt it to Rust. To me, reading Rust feels like forcing an optical nerve to see something very small.
Case in point, the code in this article.
- pattern matching is quite expressive and ubiquitous in Rust, as in "stuff on the left" is almost always a pattern even in places that you might not expect at first
- lifetime annotations, a form of generics, are quite unique and make the code more verbose/noisy if you are not familiar with them
- Rust is expression based (a bit lispy) so you might not quite see the flow of the program as well if you are used to statement based syntax
- traits are are an important part of the language and some of the methods and method chains you see there might look arbitrary, but many of them are very common. A more experienced reader can identify many of them and see the code very differently.
- there is no syntax highlighting on the article's snippets so you already have to know how to parse them
I think the author wanted to replicate the code as closely as possible, so it's more a case study than an educational piece if that makes sense?
That is incredibly hard, yes. But at least if you are ok with the overhead of GC or RC then this is a solved problem - the bug in question here wouldn't happen in Go/Swift/C#/Java/JavaScript/Python/Ruby/etc. And that is most software today.
(But when you need maximal performance and maximal safety then Rust is an incredible option!)
As far as I can tell, that’s normal Rust code, but yes it’s unreadable if you come from C++ or another language and don’t know Rust syntax. The funny thing is that this kind of code is supposed to be the readable part of Rust :-)
Part of the confusion is the very confusing (for a noob) difference between String and &str and as someone who has written C and C++, I don't have a problem with understanding references and addresses but I didn't find a very good explanation. This led me to writing code using the only way I could get it to compile that didn't crash at runtime (which was nice!) but it also didn't work.
The second issue was that being something plugging into an external daemon, at the interface level, we were getting a raw pointer and had to convert it into a string to use it. I tried so many combinations but couldn't get it to work but thanks to the maintainer of the Rust crate (who obviously knew what he was doing) he told me where my code was wrong and I fixed it.
So I guess good in that there were probably no horrific bugs, although this is much like C# or possibly Java. But yes, the strong checking doesn't stop things from not working.
And fuzzing originated in C / C++ tools and is available for them as well, probably more diverse and mature than what is available in Rust.
The point you did ignore was: Rust is being sold as magically making software safe and people selling that completely ignore the fact that logical bugs are also a thing.
Which Rust evangelists find difficult to acknowledge because security is that one thing that Rust has going for it.
So difficult that you would rather try to misdirect than to acknowledge that software written in Rust will crash. And when processing arbitrary user output, it'll crash badly when not written with care and fuzzed a lot. Kind of like C++.
It didn't for them, that's why they rewrote it.
> Rust is being sold as magically making software safe and people selling that completely ignore the fact that logical bugs are also a thing.
By whom? I don't think memory safety bugs are the only class of bugs, and neither do you, so who are you arguing against?
> Which Rust evangelists find difficult to acknowledge because security is that one thing that Rust has going for it.
Hard disagree. Rust also offers a solid type system, pain-free dependency management and all-around good tooling, just to name a few. It's much more than a "memory-safe C++" or whatever.
> And when processing arbitrary user output, it'll crash badly
If your definition of "badly" is "at a well-defined point, without any memory corruption", sure. But even here memory safety prevents what could be a much more painful debugging experience.
But yes, my point was that there are a lot of enthusiastic articles about how Rust can prevent "whole classes of bugs", which mostly gloss over the fact that there are other classes of bugs that it doesn't prevent. Of course this doesn't mislead experienced developers, but I'm not so sure about novices or less technically inclined people.
That's nothing more than a technically-dressed version of whataboutism, isn't it? Or can you show other languages that do prevent those other unnamed classes of bugs?
Are you disappointed that Rust evangelists don’t spend time talking about the fact that bugs can exist in Rust programs? Did you see the two articles posted in the last week alone about bugs that were found in Rust code? They reached the front page of HN
- https://hacks.mozilla.org/2022/06/everything-is-broken-shipp...
- https://hacks.mozilla.org/2022/06/fuzzing-rust-minidump-for-...
If you want to see criticism of Rust, I’ve found that the most informed criticism comes from people who use Rust a lot. Example - https://matklad.github.io/2020/09/20/why-not-rust.html
It may well have, but the Rust version “was faster, it crashed less, and we even knew it fixed some issues”
https://hacks.mozilla.org/2022/06/everything-is-broken-shipp...
You know, I'm a Rust evangelist. And I can tell you, why I ignore logic bugs when trying to sell Rust. Rust can prevent some logic bugs if they break invariant you managed to encode or enforce in your types. But it is a difficult topic to dive in a internet discussion. The best I could do is to bring some anecdotal data points, like &str being guaranteed to contain only valid utf8 strings. The worse issue (for a discussion) that it may be not obvious what kind of a logic error can be fixed by the type invariant discussed. So to show it I need to find some more examples of bugs that were prevented by enforcing the invariant. But you see, bugs that were prevented were not documented. It goes like this: you write code, it doesn't compile because rustc is unhappy, you fix code and it compiles nice now, you call it a day and move along. When you fixed this small issue it was not a bug, just one of a several complaints of rustc. You would need to speculate a lot about what could happen bad if rust allowed this issue to live.
We can try to get to the point from a different angle and to find logic errors in a wild, and then to speculate how type invariants might prevented them if programmers had written their types in a some particular way. But it raise a question: would they write their types in this way before they faced this particular logic error?
If I tried to talk about complex relationships of Rust and logic errors, I would need to dive into an ocean of speculations. Rust deniers are not very cooperative on this regard, and I'd bet they would just dismiss all the speculations as... well... speculations. And they would keep insist that if we cannot measure a falling frequency of logic bugs, then it is all immaterial. Some even go further and claim that if some tool doesn't prevent all bugs of a kind, then it is a useless thing.
So it is not a fun to talk about logic errors with rust deniers. Among rust evangelists the talk doesn't happen either, because they do not split bugs into two categories "logic" and "memory" bugs, they split them into "can be prevented by type invariant" and "cannot be prevented by type invariant, or too f*king difficult to". They talk about memory safety to laymen because it is something that laymen understand and it doesn't need explaining. And Rust can prevent all the bugs of this kind, so even when we talk to a people who thinks in black-and-white, we can make statements that are defensible in this Aristotelian tradition of excluded middle.
But Rust does do that, match exhaustiveness, forcing the handling of errors and the type system enables things like CreuSAT [1] using creusot [2]
[1] https://news.ycombinator.com/item?id=31780128
[2] https://github.com/xldenis/creusot
> Creusot works by translating Rust code to WhyML, the verification and specification language of Why3. Users can then leverage the full power of Why3 to (semi)-automatically discharge the verification conditions!
Units of Measure, https://github.com/iliekturtles/uom
The base properties of the language enable things that can never be done in C++.
Wow.
There are straw men and then there are straw man armies. This is probably the biggest one I've seen yet.
Hopefully in 20 years or less we will see unsafe code in the same way we view leaded gas, cigarettes and CO2. The other thing I realized in the last couple years is that eventually, everything becomes safety critical to someone at some point. That if you think you have to just protect this one system but that others can fail, is flawed on large scales. You can't tractably realize the failure graph, and nor can you prevent a seemingly innocuous thing from being used in a critical way.
The pandemic was a great lens into how human resilience kept flawed systems functioning in a way that few realized were flawed. The pharmacies in my area are still recovering only to have their problem exacerbated by more rigid controls that prevent local autonomy from making the system resilient. In this particular case, automation has made a problem worse by preventing humans from doing the little local silent repairs that they were previously doing.
1. Wearing a bicycle helmet is unpleasant, so people will cycle less and possibly use other modes of transportation such as cars. The risk of a traumatic head injury may decrease but this is offset either partially or fully by other health risks such as obesity.
2. Other road users in heavier vehicles (e.g. cars) perceive riders wearing bicycle helmets as less vulnerable, and so keep less distance when overtaking them, thus wearing a helmet increases the risk of collision.
I don't think I've ever heard someone say they won't wear a bicycle helmet because it would make them personally drive more dangerously.
I personally strongly oppose mandatory bike helmets. I think one of the reasons so many people own and ride bikes in my country (The Netherlands) is that we don't have mandatory helmets. Making helmets mandatory for people inside cars would also prevent or reduce head injuries (and indeed that is why they are worn by race car drivers) so if we ever introduce mandatory bicycle helmets here perhaps we can introduce mandatory car helmets at the same time, to ensure that people won't just take a car instead.
I don't think that's true. Sure, there are zealots who oversell things, but reasonable people (including those who are actual leaders in the Rust community) are realistic about the classes of bugs that Rust eliminates, as well as those Rust doesn't help with.
This is a fun mistake. It’s “Palate cleanser”.
1. Rust does not protect you at compile time against "index out of bounds" errors. If you want that, you'll want a dependently-typed language like Idris. Which still wouldn't help much in this case.
2. However, Rust catches index errors perfectly well at runtime, and performs a controlled panic. This does not result in a privilege escalation, but it may cause a denial of service. So fuzzing in (safe) Rust is normally a "lower stakes" activity.
3. "cargo fuzz" is super easy to use, and AFL is pretty amazing.
4. If you think you can parse complex, badly-documented low-level binary formats without ever having an index error, you're probably wildly overconfident. In the case of the minidump parser, it's worse: The data was generated by a crashing process, and in many cases, the data caused the crash.
Let's look at what the author concludes:
> And what did we screw up? Some legit stuff! It’s Rust code, so I am fairly confident none of the issues were security concerns, but they were definitely quality of implementation issues, and could have been used to at very least denial-of-service the minidump processor.
A whole bunch of issues, probably none of them leading to security escalations. And unlike many C fuzzing experiences, it was largely painless:
> By comparison I am absolutely thriving under “Yeah you can deterministically trip this assertion with this tiny input you can just check in as a unit test”.
This happens because Rust catches most of these errors very early, using assertions in the standard library. So your fuzz reports often need to be tracked for only a few lines.
TL;Dr: Parsing complex binary formats usually involves subtle bugs. Rust does not promise to catch index-out-of-bounds at compile-time, but it catches them at runtime. This makes fuzzing easy and productive.
Overall, I'd call this a success story. The author underwent an inevitably humbling experience for low stakes under controlled conditions. And now the library can parse corrupted examples of an incredibly nasty format, with high confidence that the worst thing that will happen is a runtime error (not even a DoS).
That said, I never want to see another rounding bug in financial statements that happened because some piece of code implicitly converted a string to a float and it kinda almost worked every time. To quote a cliche, I'm too old for that shit.
I mean, other than []<>(){}() being a valid C++ expression [0], C++ hasn't even made any notable sygil-related syntax changes recently. Sure, some keywords were added (auto, constexpr, co_yield...) but I don't see how that leads to "ugly" syntax.
[0]: That's a lambda capturing no state, having no template parameters, taking no arguments, with an empty body, finally being called immediately with no arguments. But half of that is optional. A more usual lambda would look like [foo](int bar) { return foo+bar; }.
On the older side: the initialization list syntax has always been ugly (and bug-prone, due to the unintuitive evaluation order.)
template< typename T >
struct foo<T, std::void_t<decltype(++std::declval<T&>() )>>
: std::true_type { };
looks terrible and that's just basic.https://rust-lang.github.io/rfcs/2515-type_alias_impl_trait....
In fact, a lot of what the Rust syntax ugly was taken from C++, and it took most of what makes C++ ugly. So the entire discussion is just amusing.
Yes, the article is overly technical and detailed, but it captures very well the eureka moment of understanding Rust’s memory management. Some aspects are subtle yet profound.
I am always happy to take a little more pain at code-writing and compile-time in exchange for more safety guarantees at run time; Rust is great in this regard.
This is classically a problem of GUI systems, where you have a large number of interlinked on-screen objects constantly undergoing modification.
- wait for input
- a dispatcher take any input and turn it into a queue of model update actions
- pop queue to perform latest model updates.
- trigger a full tree rendering, passing the new model
- start again
So:
- the model is a dumb static data structure
- the dispatcher is the responsible for model updates and is a single entry point for the data flow
- the UI and the model don't know about each other
That what ELM, react and so on do now. Monodirectional MVC. It's hard to optimize the rendering though, but it's way easier to reason about when concurrency gets high.
I agree the Elm model makes it easy to reason about it.
Just because we pass "the state of the world" each time instead of mutating it, does not mean that we need a full tree rendering. That is an implementation detail.
We can update just the relevant part. If we think of it mostly as pure functions a lot of it is easier to memoize and — for same input — it is possible to return the same output straight away.
And of course, forcing a purely immutable model comes with its own unique challenges.
It's a terrible idea to post a photo of your child with her name on your public blog.
It's one of those subtle security things that the rust compiler helps with; but not here, sadly.
It’s pointing out that we are pushing into this vector
which needs references into “the AST”, but we haven’t
declared in our signature that the ast::Ty must actually
from “the AST”.
I think someone needs to write a typechecker for English to help out the author of this sentence.