Various "safe" languages are just as vulnerable to denial-of-service vulnerabilities as C/C++. Since I started fuzzing Go projects I've come across numerous parsers of untrusted data that flat-out crash if a slice is accessed out-of-bounds. Deep recursions (A() calls B() calls A() etc) can crash both Go and Rust. And in probably every general programming language you can implement logic that leads to excessive resource allocation or computation, and unintended infinite loops. These kinds of bugs can obviously be problematic for network-facing components. A royal use of runtime assertions in debug builds for arbitrary invariants/conditions combined with fuzzing usually uncovers many more bugs.
If it is a server process, it can even corrupt data for days until someone actually notices it, if ever.
Or they just crash in a totally unrelated memory segment a couple of hours later.
Crashing when the out-of-bounds takes place is a much better outcome.
I stared at the code for some time and couldn't see any mishandled edge cases. It was then I realized that I could automatically generate an arbitrary amount of unit tests, because it's simple enough to call strcat and encode that to get the correct result. So, that's what I did: generate two strings of ACTG, encode them, concat, and compare against the result against that of the encoding of the two strings concatenated. Sure enough, a mishandled edge case popped up after trying a couple hundred strings. There was probably no way I could have found that bug without this approach.
It's a really simple concept, but you need a couple of things: first, a very wide input space. Next, you need an alternate way of verifying the result. If you just want your program to not crash, you get this for free. In my case above, I could apply the two functions (concatenate/encode) in the other order to easily calculate the expected result. If you have these two things, a fuzzer is the logical thing to write to make sure your code is rock solid.