[HN Gopher] Matt Godbolt sold me on Rust by showing me C++
       ___________________________________________________________________
        
       Matt Godbolt sold me on Rust by showing me C++
        
       Author : LorenDB
       Score  : 257 points
       Date   : 2025-05-06 17:51 UTC (5 hours ago)
        
 (HTM) web link (www.collabora.com)
 (TXT) w3m dump (www.collabora.com)
        
       | favorited wrote:
       | Side note, if anyone is interested in hearing more from Matt, he
       | has a programming podcast with Ben Rady called Two's Complement.
       | 
       | https://www.twoscomplement.org
        
         | badbart14 wrote:
         | +1, especially loved the episode from a couple months back
         | about using AI tools in development. Really got me thinking
         | differently about the role of AI in a developer's workflow and
         | how software development will evolve.
        
           | penguin_booze wrote:
           | I can't see the publish date on the episodes.
        
             | chucksmash wrote:
             | https://www.twoscomplement.org/#podcast/pair-programming-
             | wit...
        
       | markus_zhang wrote:
       | What if we have a C that removes the quirks without adding too
       | much brain drain?
       | 
       | So no implicit type conversions, safer strings, etc.
        
         | wffurr wrote:
         | This seems like such an obvious thing to have - where is it?
         | Zig, Odin, etc. all seem much more ambitious.
        
           | steveklabnik wrote:
           | There have been attempts over the years. See here, a decade
           | ago: https://blog.regehr.org/archives/1287
           | 
           | > eventually I came to the depressing conclusion that there's
           | no way to get a group of C experts -- even if they are
           | knowledgable, intelligent, and otherwise reasonable -- to
           | agree on the Friendly C dialect. There are just too many
           | variations, each with its own set of performance tradeoffs,
           | for consensus to be possible.
        
           | zyedidia wrote:
           | I think the only "C replacement" that is comparable in
           | complexity to C is [Hare](https://harelang.org/), but several
           | shortcomings make it unsuitable as an actual C replacement in
           | many cases (little/no multithreading, no support for
           | macOS/Windows, no LLVM or GCC support, etc.).
        
             | Zambyte wrote:
             | And why do you think Zig (and Odin, but I'm not really
             | familiar with that one) is not comparable in complexity to
             | C? If you start with C, replace the preprocessor language
             | with the host language, replace undefined behavior with
             | illegal behavior (panics in debug builds), add different
             | pointer types for different types of pointers (single
             | object pointers, many object pointers, fat many object
             | pointers (slices), nullable pointers), and make a few
             | syntactic changes (types go after the names of values in
             | declarations, pointer dereference is a postfix operator,
             | add defer to move expressions like deallocation to the end
             | of the scope) and write a new standard library, you pretty
             | much have Zig.
        
           | IshKebab wrote:
           | I think if you are going to fix C's footguns you'll have to
           | change so much you end up with a totally new language anyway,
           | and then why not be ambitious? It costs a lot to learn a new
           | language and people aren't going to bother if the only
           | benefit it brings is things that can sort of mostly be caught
           | with compiler warnings and static analysis.
        
         | alexchamberlain wrote:
         | I'm inferring that you think Rust adds too much brain drain? If
         | so, what?
        
           | GardenLetter27 wrote:
           | The borrow checker rejects loads of sound programs - just
           | read https://rust-unofficial.github.io/too-many-lists/
           | 
           | Aliasing rules can also be problematic in some circumstances
           | (but also beneficial for compiler optimisations).
           | 
           | And the orphan rule is also quite restrictive for adapting
           | imported types, if you're coming from an interpreted
           | language.
           | 
           | https://loglog.games/blog/leaving-rust-gamedev/ sums up the
           | main issues nicely tbh.
        
             | IshKebab wrote:
             | > The borrow checker rejects loads of sound programs
             | 
             | I bet assembly programmers said the same about C!
             | 
             | Every language has relatively minor issues like these.
             | Seriously pick a language and I can make a similar list.
             | For C it will be a _very_ long list!
        
           | leonheld wrote:
           | I love Rust, but I after doing it for a little while, I
           | completely understand the "brain drain" aspect... yes, I get
           | significantly better programs, but it is tiring to fight the
           | borrow-checker sometimes. Heck, I currently _am_
           | procrastinating instead of going into the ring.
           | 
           | Anyhow, I won't go back to C++ land. Better this than
           | whatever arcane, 1000-line, template-hell error message that
           | kept me fed when I was there.
        
         | LorenDB wrote:
         | Walter Bright will probably show up soon to plug D's BetterC
         | mode, but if he doesn't, still check it out.
         | 
         | https://dlang.org/spec/betterc.html
        
         | cogman10 wrote:
         | I've seen this concept tried a few times (For example, MS tried
         | it with Managed C++). The inevitable problem you run into is
         | any such language isn't C++. Because of that, you end up
         | needing to ask, "why pick this unpopular half C/C++
         | implementation and not Rust/go/D/Java/python/common
         | lisp/haskell."
         | 
         | A big hard to solve problem is you are likely using a C because
         | of the ecosystem and/or the performance characteristics.
         | Because of the C header/macro situation that becomes just a
         | huge headache. All the sudden you can't bring in, say, boost
         | because the header uses the quirks excluded from your smaller C
         | language.
        
         | mamcx wrote:
         | If you can live without much of the ecosystem (specially if has
         | async) there is way to write rust very simple.
         | 
         | The core of Rust is actually very simple: Struct, Enum,
         | Functions, Traits.
        
         | dlachausse wrote:
         | Swift is really great these days and supports Windows and
         | Linux. It almost feels like a scripting language other than the
         | compile time of course.
        
           | smt88 wrote:
           | There is no universe where I'm doing to use Apple tooling on
           | a day to day basis. Their DX is the worst among big tech
           | companies by far.
        
             | dlachausse wrote:
             | They have quite robust command line tooling and a good VS
             | Code plugin now. You don't need to use Xcode anymore for
             | Swift.
        
           | kelnos wrote:
           | I still have a hard time adopting a language/ecosystem that
           | was originally tied to a particular platform, and is still
           | "owned" by the owners of that platform.
           | 
           | Sun actually did it right with Java, recognizing that if they
           | mainly targeted SunOS/Solaris, no one would use it. And even
           | though Oracle owns it now, it's not really feasible for them
           | to make it proprietary.
           | 
           | Apple didn't care about other platforms (as usual) for quite
           | a long time in Swift's history. Microsoft was for years
           | actively hostile toward attempts to run .NET programs on
           | platforms other than Windows. Regardless of Apple's or MS's
           | current stance, I can't see myself ever bothering with Swift
           | or C#/F#/etc. There are too many other great choices with
           | broad platform and community support, that aren't closely
           | tied to a corporation.
        
             | hmry wrote:
             | .NET recently had a (very) minor controversy for inserting
             | what amounts to a GitHub Copilot ad into their docs. So
             | yeah, it sure feels like "once a corporate language, always
             | a corporate language", even if it's transferred to a
             | nominally independent org. It might not be entirely
             | rational, but I certainly feel uncomfortable using Swift or
             | .NET.
        
         | o11c wrote:
         | I too have been thinking a lot about a minimum viable
         | improvement over C. This requires actually being able to
         | incrementally port your code across:
         | 
         | * "No implicit type conversions" is trivial, and hardly worth
         | mentioning. Trapping on both signed and unsigned overflow is
         | viable but for hash-like code opting in to wrapping is
         | important.
         | 
         | * "Safer strings" means completely different things to
         | different people. Unfortunately, the need to support porting to
         | the new language means there is little we can do by default,
         | given the _huge_ amount of existing code. We can however, add
         | _new_ string types that act relatively uniformly so that the
         | code can be ported incrementally.
         | 
         | * For the particular case of arrays, remember that there are at
         | least 3 different ways to compute its length (sentinel, size,
         | end-pointer). All of these will need proper typing support.
         | Particularly remember functions that take things like `(begin,
         | middle end)`, or `(len, arr1[len], arr2[len])`.
         | 
         | * Support for nontrivial trailing array-or-other datums, and
         | also other kinds of "multiple objects packed within a single
         | allocation", is essential. Again, most attempted replacements
         | fail badly.
         | 
         | * Unions, unfortunately, will require much fixing. Most only
         | need a tag logic (or else replacement with bitcasting), but
         | `sigval` and others like it are fundamentally global in nature.
         | 
         | * `va_list` is also essential to support since it is very
         | widely used.
         | 
         | * The lack of proper C99 floating-point support, even in
         | $CURRENTYEAR, means that compile-to-C implementations will not
         | be able to support it properly either, even if the relevant
         | operations are all properly defined in the new frontend to take
         | an extra "rounding mode" argument. Note that the platform ABI
         | matters here.
         | 
         | * There are quite a few things that macros are used for, but
         | ultimately this probably _is_ a finite set so should be
         | possible to automatically convert with a SMOC.
         | 
         | Failure to provide a good porting story is the #1 mistake most
         | new languages make.
        
         | nitwit005 wrote:
         | Because it's easier to add a warning or error. Don't like
         | implicit conversions? Add a compiler flag, and the issue is
         | basically gone.
         | 
         | Safer strings is harder, as it gets into the general memory
         | safety problem, but people have tried adding safer variants of
         | all the classic functions, and warnings around them.
        
         | Certhas wrote:
         | Maybe just unsafe rust?
        
       | writebetterc wrote:
       | Yes, Rust is better. Implicit numeric conversion is terrible.
       | However, don't use atoi if you're writing C++ :-). The STL has
       | conversion functions that will throw, so separate problem.
        
         | titzer wrote:
         | > Implicit numeric conversion is terrible.
         | 
         | It's bad if it alters values (e.g. rounding). Promotion from
         | one number representation to another (as long as it preserves
         | values) isn't bad. This is trickier than it might seem, but
         | Virgil has a good take on this (https://github.com/titzer/virgi
         | l/blob/master/doc/tutorial/Nu...). Essentially, it only
         | implicitly promotes values in ways that don't lose numeric
         | information and thus are always reversible.
         | 
         | In the example, Virgil won't let you pass "1000.00" to an
         | integer argument, but will let you pass "100" to the double
         | argument.
        
           | plus wrote:
           | Aside from the obvious bit size changes (e.g. i8 -> i16 ->
           | i32 -> i64, or f32 -> f64), there is no "hierarchy" of types.
           | Not all ints are representable as floats. u64 can represent
           | up to 2^64 - 1, but f64 can only represent up to 2^53 with
           | integer-level precision. This issue may be subtle, but Rust
           | is all about preventing subtle footguns, so it does not let
           | you automatically "promote" integers to float - you must be
           | explicit (though usually all you need is an `as f64` to
           | convert).
        
             | mananaysiempre wrote:
             | > Aside from the obvious bit size changes (e.g. i8 -> i16
             | -> i32 -> i64, or f32 -> f64), there is no "hierarchy" of
             | types.
             | 
             | Depends on what you want from such a hierarchy, of course,
             | but there is for example an injection i32 -> f64 (and if
             | you consider the i32 operations to be undefined on
             | overflow, then it's also a homomorphism wrt addition and
             | multiplication). For a more general view, various Schemes'
             | takes on the "numeric tower" are informative.
        
               | titzer wrote:
               | Virgil allows the maximum amount of implicit int->float
               | injections that don't change values and allows casts (in
               | both directions) that check if rounding altered a value.
               | It thus guarantees that promotions and (successful) casts
               | can't alter program behavior. Given any number in
               | representation R, promotion or casting to type N and then
               | casting back to R will return the same value. Even for
               | NaNs with payloads (which can happen with float <->
               | double).
        
             | titzer wrote:
             | Yep, Virgil only implicitly promotes integers to float when
             | rounding won't change the value.                    // OK
             | implicit promotions          def x1: i20;          def f1:
             | float = x1;          def x2: i21;          def f2: float =
             | x2;          def x3: i22;          def f3: float = x3;
             | def x4: i23;          def f4: float = x4;               //
             | compile error!          def x5: i24;          def f5: float
             | = x5; // requires rounding
             | 
             | This also applies to casts, which are dynamically checked.
             | // runtime error if rounding alters value          def x5:
             | i24;          def f5: float = float.!(x5);
        
         | roelschroeven wrote:
         | The numeric conversion functions in the STL are terrible. They
         | will happily accept strings with non-numeric characters in
         | them: they will convert "123abc" to 123 without giving an
         | error. The std::sto* functions will also ignore leading
         | whitespace.
         | 
         | Yes, you can ask the std::sto* functions for the position where
         | they stopped because of invalid characters and see if that
         | position is the end of the string, but that is much more
         | complex than should be needed for something like that.
         | 
         | These functions don't _convert_ a string to a number, they try
         | to _extract_ a number from a string. I would argue that most of
         | the time, that 's not what you want. Or at least, most of the
         | time it's not what I need.
         | 
         | atoi has the same problem of course, but even worse.
        
         | im3w1l wrote:
         | Forcing people to explicitly casts everything all the time
         | means that dangerous casts don't stand out as much. That's an L
         | for rust imo.
        
       | dvratil wrote:
       | The one thing that sold me on Rust (going from C++) was that
       | there is a single way errors are propagated: the Result type. No
       | need to bother with exceptions, functions returning bool,
       | functions returning 0 on success, functions returning 0 on error,
       | functions returning -1 on error, functions returning negative
       | errno on error, functions taking optional pointer to bool to
       | indicate error (optionally), functions taking reference to
       | std::error_code to set an error (and having an overload with the
       | same name that throws an exception on error if you forget to pass
       | the std::error_code)...I understand there's 30 years of history,
       | but it still is annoying, that even the standard library is not
       | consistent (or striving for consistency).
       | 
       | Then you top it on with `?` shortcut and the functional interface
       | of Result and suddenly error handling becomes fun and easy to
       | deal with, rather than just "return false" with a "TODO: figure
       | out error handling".
        
         | jasonjmcghee wrote:
         | unfortunately it's not so simple. that's the convention.
         | depending on the library you're using it might be a special
         | type of Error, or special type of Result, something needs to be
         | transformed, `?` might not work in that case (unless you
         | transform/map it), etc.
         | 
         | I like rust, but its not as clean in practice, as you describe
        
           | koakuma-chan wrote:
           | You can use anyhow::Result, and the ? will work for any
           | Error.
        
           | ryandv wrote:
           | There are patterns to address it such as creating your own
           | Result type alias with the error type parameter (E) fixed to
           | an error type you own:                   type Result<T> =
           | result::Result<T, MyError>;              #[derive(Debug)]
           | enum MyError {             IOError(String)             // ...
           | }
           | 
           | Your owned (i.e. not third-party) Error type is a sum type of
           | error types that might be thrown by other libraries, with a
           | newtype wrapper (`IOError`) on top.
           | 
           | Then implement the `From` trait to map errors from third-
           | party libraries to your own custom Error space:
           | impl From<io::Error> for MyError {             fn from(e:
           | io::Error) -> MyError {
           | MyError::IOError(e.to_string())             }         }
           | 
           | Now you can convert any result into a single type that you
           | control by transforming the errors:                   return
           | sender             .write_all(msg.as_bytes())
           | .map_err(|e| e.into());
           | 
           | There is a little boilerplate and mapping between error
           | spaces that is required but I don't find it that onerous.
        
         | dvt wrote:
         | Maybe contrarian, but imo the `Result` type, while kind of
         | nice, still suffers from plenty of annoyances, including
         | sometimes not working with the (manpages-approved) `dyn Error`,
         | sometimes having to `into()` weird library errors that don't
         | propagate properly, or worse: `map_err()` them; I mean, at this
         | point, the `anyhow` crate is basically mandatory from an
         | ergonomics standpoint in every Rust project I start. Also, `?`
         | doesn't work in closures, etc.
         | 
         | So, while this is an improvement over C++ (and that is not
         | saying much at all), it's still implemented in a pretty clumsy
         | way.
        
           | maplant wrote:
           | ? definitely works in closures, but it often takes a little
           | finagling to get working, like specifying the return type of
           | the closure or setting the return type of a collect to a
           | Result<Vec<_>>
        
           | ackfoobar wrote:
           | > the `anyhow` crate is basically mandatory from an
           | ergonomics standpoint in every Rust project I start
           | 
           | If you use `anyhow`, then all you know is that the function
           | may `Err`, but you do not know how - this is no better than
           | calling a function that may `throw` any kind of `Throwable`.
           | Not saying it's bad, it is just not that much different from
           | the error handling in Kotlin or C#.
        
             | efnx wrote:
             | Yes. I prefer 'snafu' but there are a few, and you could
             | always roll your own.
        
               | shepmaster wrote:
               | Yeah, with SNAFU I try to encourage people going all-in
               | on very fine-grained error types. I love it
               | (unsurprisingly).
        
               | smj-edison wrote:
               | +1 for snafu. It lets you blend anyhow style errors for
               | application code with precise errors for library code.
               | .context/.with_context is also a lovely way to propagate
               | errors between different Result types.
        
               | bonzini wrote:
               | How does that compare to "this error for libraries and
               | anyhow for applications"?
        
               | smj-edison wrote:
               | You don't have to keep converting between error types :)
        
             | jbritton wrote:
             | I know a 'C' code base that treats all socket errors the
             | same and just retries for a limited time. However there are
             | errors that make no sense to retry, like invalid socket or
             | socket not connected. It is necessary to know what socket
             | error occurred. I like how the Posix API defines an errno
             | and documents the values. Of course this depends on
             | accurate documentation.
        
               | XorNot wrote:
               | This is an IDE/documentation problem in a lot of cases
               | though. No one writes code badly intentionally, but we
               | are time constrained - tracking down every type of error
               | which can happen and what it means is time consuming and
               | you're likely to get it wrong.
               | 
               | Whereas going with "I probably want to retry a few times"
               | is guessing that most of your problems are the common
               | case, but you're not entirely sure the platform you're on
               | will emit non-commoncases with sane semantics.
        
             | Yoric wrote:
             | Yeah, `anyhow` is basically Go error handling.
             | 
             | Better than C, sufficient in most cases if you're writing
             | an app, to be avoided if you're writing a lib. There are
             | alternatives such as `snafu` or `thiserror` that are better
             | if you need to actually catch the error.
        
           | singingboyo wrote:
           | There's some space for improvement, but really... not a lot?
           | Result is a pretty basic type, sure, but needing to choose a
           | dependency to get a nicer abstraction is not generally
           | considered a problem for Rust. The stdlib is not really
           | batteries included.
           | 
           | Doing error handling properly is hard, but it's a lot harder
           | when error types lose information (integer/bool returns) or
           | you can't really tell what errors you might get (exceptions,
           | except for checked exceptions which have their own issues).
           | 
           | Sometimes error handling comes down to "tell the user", where
           | all that info is not ideal. It's too verbose, and that's when
           | you need anyhow.
           | 
           | In other cases where you need details, anyhow is terrible.
           | Instead you want something like thiserror, or just roll your
           | own error type. Then you keep a lot more information, which
           | might allow for better handling. (HttpError or IoError - try
           | a different server? ParseError - maybe a different parse
           | format? etc.)
           | 
           | So I'm not sure it's that Result is clumsy, so much that
           | there are a lot of ways to handle errors. So you have to pick
           | a library to match your use case. That seems acceptable to
           | me?
           | 
           | FWIW, errors not propagating via `?` is entirely a problem on
           | the error type being propagated to. And `?` in closures does
           | work, occasionally with some type annotating required.
        
           | skrtskrt wrote:
           | A couple of those annoyances are just library developers
           | being too lazy to give informative error types which is far
           | from a Rust-specific problem
        
         | tubs wrote:
         | And panics?
        
         | mdf wrote:
         | Generally, I agree the situation with errors is much better in
         | Rust in the ways you describe. But, there are also panics which
         | you can catch_unwind[1], set_hook[2] for, define a
         | #[panic_handler][3] for, etc.
         | 
         | [1] https://doc.rust-lang.org/std/panic/fn.catch_unwind.html
         | 
         | [2] https://doc.rust-lang.org/std/panic/fn.set_hook.html
         | 
         | [3] https://doc.rust-lang.org/nomicon/panic-handler.html
        
           | ekidd wrote:
           | Yeah, in anything but heavily multi-threaded servers, it's
           | usually best to immediately crash on a panic. Panics don't
           | mean "a normal error occurred", they mean, "This program is
           | cursed and our fundamental assumptions are wrong." So it's
           | normal for a unit test harness to catch panics. And you may
           | occasionally catch them and kill an entire client connection,
           | sort of the way Erlang handles major failures. But most
           | programs should just exit immediately.
        
         | jeroenhd wrote:
         | The result type does make for some great API design, but
         | SerenityOS shows that this same paradigm also works fine in
         | C++. That includes something similar to the ? operator, though
         | it's closer to a raw function call.
         | 
         | SerenityOS is the first functional OS (as in "boots on actual
         | hardware and has a GUI") I've seen that dares question the
         | 1970s int main() using modern C++ constructs instead, and the
         | API is simply a lot better.
         | 
         | I can imagine someone writing a better standard library for C++
         | that works a whole lot like Rust's standard library does.
         | Begone with the archaic integer types, make use of the power
         | your language offers!
         | 
         | If we're comparing C++ and Rust, I think the ease of use of
         | enum classes/structs is probably a bigger difference. You can
         | get pretty close, but Rust avoids a lot of boilerplate that
         | makes them quite usable, especially when combined with the
         | match keyword.
         | 
         | I think c++, the language, is ready for the modern world.
         | However, c++, the community, seems to be struck at least 20
         | years in the past.
        
           | jchw wrote:
           | Google has been doing a very similar, but definitely somewhat
           | uglier, thing with StatusOr<...> and Status (as seen in absl
           | and protobuf) for quite some time.
           | 
           | A long time ago, there was talk about a similar concept for
           | C++ based on exception objects in a more "standard" way that
           | could feasibly be added to the standard library, the
           | expected<T> class. And... in C++23, std::expected _does_
           | exist[1], and you don 't need to use exception objects or
           | anything awkward like that, it can work with arbitrary error
           | types just like Result. Unfortunately, it's so horrifically
           | late to the party that I'm not sure if C++23 will make it to
           | critical adoption quickly enough for any major C++ library to
           | actually adopt it, unless C++ has another massive resurgence
           | like it did after C++11. That said, if you're writing C++
           | code and you want a "standard" mechanism like the Result
           | type, it's probably the closest thing there will ever be.
           | 
           | [1]: https://en.cppreference.com/w/cpp/utility/expected
        
             | a_t48 wrote:
             | There's a few backports around, not quite the same as
             | having first class support, though.
        
               | jchw wrote:
               | I believe the latest versions of GCC, Clang, MSVC and
               | XCode/AppleClang all support std::expected, in C++23
               | mode.
        
             | loeg wrote:
             | Facebook's Folly has a similar type: folly::Expected
             | (dating to 2016).
        
             | CJefferson wrote:
             | I had a look. In classic C++ style, if you use *x to get
             | the 'expected' value, when it's an error object (you forgot
             | to check first and return the error), it's undefined
             | behaviour!
             | 
             | Messing up error handling isn't hard to do, so putting
             | undefined behaviour here feels very dangerous to me, but it
             | is the C++ way.
        
           | d_tr wrote:
           | C++ carries so much on its back and this makes its evolution
           | over the past decade even more impressive.
        
           | Rucadi wrote:
           | I created a library "cpp-match" that tries to bring the "?"
           | operator into C++, however it uses a gnu-specific feature
           | (https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html), I
           | did support msvc falling-back to using exceptions for the
           | short-circuit mechanism.
           | 
           | However it seems like C++ wants to only provide this kind of
           | pattern via monadic operations.
        
         | zozbot234 wrote:
         | > The one thing that sold me on Rust (going from C++) was that
         | there is a single way errors are propagated: the Result type.
         | No need to bother with exceptions
         | 
         | This isn't really true since Rust has panics. It would be nice
         | to have out-of-the-box support for a "no panics" subset of
         | Rust, which would also make it easier to properly support
         | linear (no auto-drop) types.
        
           | alexeldeib wrote:
           | that's kind of a thing with https://docs.rs/no-
           | panic/latest/no_panic/ or no std and custom panic handlers.
           | 
           | not sure what the latest is in the space, if I recall there
           | are some subtleties
        
             | zozbot234 wrote:
             | That's a neat hack, but it would be a lot nicer to have
             | explicit support as part of the language.
        
               | nicce wrote:
               | The problem is with false positives. Even if you clearly
               | see that some function will never panic (but it uses some
               | feature which may panic), compiler might not always see
               | that. If compiler says that there are no panics, then
               | there are no panics, but is it enough to add as part of
               | the language if you need to mostly avoid using features
               | that might panic?
        
               | kbolino wrote:
               | That's going to be difficult because the language itself
               | requires panic support to properly implement indexing,
               | slicing, and integer division. There are checked methods
               | that can be used instead, but to truly eliminate panics,
               | the ordinary operators would have to be banned when used
               | with non-const arguments, and this restriction would have
               | to propagate to all dependencies as well.
        
           | arijun wrote:
           | `panic` isn't really an error that you have to (or can)
           | handle, it's for unrecoverable errors. Sort of like C++
           | assertions.
           | 
           | Also there is the no_panic crate, which uses macros to
           | require the compiler to prove that a given function cannot
           | panic.
        
             | nicce wrote:
             | I would say that Segmentation Fault is better comparison
             | with C++ :-D
        
             | marcosdumay wrote:
             | Well, kinda. It's more similar to RuntimeException in Java,
             | in that there are times where you do actually want to catch
             | and recover from them.
             | 
             | But on those places, you better know exactly what you are
             | doing.
        
           | kelnos wrote:
           | I wish more people (and crate authors) would treat panic!()
           | as it really should be treated: only for absolutely
           | unrecoverable errors that indicate that some sort of state is
           | corrupted and that continuing wouldn't be safe from a data-
           | or program-integrity perspective.
           | 
           | Even then, though, I do see a need to catch panics in some
           | situations: if I'm writing some sort of API or web service,
           | and there's some inconsistency in a particular request (even
           | if it's because of a bug I've written), I probably really
           | would prefer only that request to abort, not for the entire
           | process to be torn down, terminating any other in-flight
           | requests that might be just fine.
           | 
           | But otherwise, you really should just not be catching panics
           | at all.
        
             | tcfhgj wrote:
             | would you consider panics acceptable when you think it
             | cannot panic in practice? e.g. unwraping/expecting a value
             | for a key in a map when you inserted that value before and
             | know it hasn't been removed?
             | 
             | you could have a panic though, if you wrongly make
             | assumptions
        
               | pdimitar wrote:
               | I don't speak for anyone else but I'm not using `unwrap`
               | and `expect`. I understand the scenario you outlined but
               | I've accepted it as a compromise and will `match` on a
               | map's fetching function and will have an `Err` branch.
               | 
               | I will fight against program aborts as hard as I possibly
               | can. I don't mind boilerplate to be the price paid and
               | will provide detailed error messages even in such obscure
               | error branches.
               | 
               | Again, speaking only for myself. My philosophy is: the
               | program is no good for me dead.
        
               | von_lohengramm wrote:
               | > the program is no good for me dead
               | 
               | That may be true, but the program may actually be bad for
               | you if it does something unexpected due to an unforeseen
               | state.
        
               | pdimitar wrote:
               | Agreed, that's why I don't catch panics either -- if we
               | get to that point I'm viewing the program as corrupted.
               | I'm simply saying that I do my utmost to never use
               | potentially panicking Rust API and prefer to add
               | boilerplate for `Err` branching.
        
               | conradludgate wrote:
               | Not the same person, but I first try and figure out an
               | API that allows me to not panic in the first place.
               | 
               | Panics are a runtime memory safe way to encode an
               | invariant, but I will generally prefer a compile time
               | invariant if possible and not too cumbersome.
               | 
               | However, yes I will panic if I'm not already using unsafe
               | and I can clearly prove the invariant I'm working with.
        
           | codedokode wrote:
           | It's pretty difficult to have no panics, because many
           | functions allocate memory and what are they supposed to do
           | when there is no memory left? Also many functions use
           | addition and what is one supposed to do in case of overflow?
        
             | nicce wrote:
             | Additions are easy. By default they are wrapped, and you
             | can make them explicit with checked_ methods.
             | 
             | Assuming that you are not using much recursion, you can
             | eliminate most of the heap related memory panics by adding
             | limited reservation checks for dynamic data, which is
             | allocated based on user input/external data. You should
             | also use statically sized types whennever possible. They
             | are also faster.
        
               | codedokode wrote:
               | Wrapping on overflow is wrong because this is not the
               | math we expect. As a result, errors and vulnerabilities
               | occur (look at Linux kernel for example).
        
               | nicce wrote:
               | It depends on the context. Of course the result may cause
               | vulnerabilities if the program logic in bad context
               | depends on it. But yeah, generally I would agree.
        
             | pdimitar wrote:
             | Don't know about your parent poster but I didn't take it
             | 100% literally. Obviously if there's no memory left then
             | you crash; the kernel would likely murder your program half
             | a second later anyway.
             | 
             | But for arithmetics Rust has non-aborting bound checking
             | API, if my memory serves.
             | 
             | And that's what I'm trying hard to do in my Rust code f.ex.
             | don't frivolously use `unwrap` or `expect`, ever. And just
             | generally try hard to never use an API that can crash. You
             | can write a few error branches that might never get
             | triggered. It's not the end of the world.
        
               | wahern wrote:
               | Dealing with integer overflow is much more burdensome
               | than dealing with allocation failure, IME. Relatively
               | speaking, allocation failure is closer to file descriptor
               | limits in terms of how it effects code structure. But
               | then I mostly use C when I'm not using a scripting
               | language. In languages like Rust and C++ there's alot of
               | hidden allocation in the high-level libraries that seem
               | to be popular, perhaps because the notion that "there's
               | nothing you can do" has infected too many minds.
               | 
               | Of course, just like with opening files or integer
               | arithmetic, if you don't pay any attention to handling
               | the errors up front when writing your code, it can be an
               | onerous if not impossible to task to refactor things
               | after the fact.
        
               | pdimitar wrote:
               | Oh I agree, don't get me wrong. Both are pretty gnarly.
               | 
               | I was approaching these problems strictly from the point
               | of view of what can Rust do today really, nothing else.
               | To me having checked and non-panicking API for integer
               | overflows / underflows at least gives you some agency.
               | 
               | If you don't have memory, well, usually you are cooked.
               | Though one area where Rust can become even better there
               | is to give us some API to reserve more memory upfront,
               | maybe? Or I don't know, maybe adopt some of the memory-
               | arena crates in stdlib.
               | 
               | But yeah, agreed. Not the types of problems I want to
               | have anymore (because I did have them in the past).
        
             | Arnavion wrote:
             | >many functions allocate memory and what are they supposed
             | to do when there is no memory left?
             | 
             | Return an AllocationError. Rust unfortunately picked the
             | wrong default here for the sake of convenience, along with
             | the default of assuming a global allocator. It's now trying
             | to add in explicit allocators and allocation failure
             | handling (A:Allocator type param) at the cost of splitting
             | the ecosystem (all third-party code, including parts of
             | libstd itself like std::io::Read::read_to_end, only work
             | with A=GlobalAlloc).
             | 
             | Zig for example does it right by having explicit allocators
             | from the start, plus good support for having the allocator
             | outside the type (ArrayList vs ArrayListUnmanaged) so that
             | multiple values within a composite type can all use the
             | same allocator.
             | 
             | >Also many functions use addition and what is one supposed
             | to do in case of overflow?
             | 
             | Return an error ( https://doc.rust-
             | lang.org/stable/std/primitive.i64.html#meth... ) or a
             | signal that overflow occurred ( https://doc.rust-
             | lang.org/stable/std/primitive.i64.html#meth... ). Or use
             | wrapping addition ( https://doc.rust-
             | lang.org/stable/std/primitive.i64.html#meth... ) if that
             | was intended.
             | 
             | Note that for the checked case, it is possible to have a
             | newtype wrapper that impls std::ops::Add etc, so that you
             | can continue using the compact `+` etc instead of the
             | cumbersome `.checked_add(...)` etc. For the wrapping case
             | libstd already has such a newtype: std::num::Wrapping.
             | 
             | Also, there is a clippy lint for disallowing `+` etc (
             | https://rust-lang.github.io/rust-
             | clippy/master/index.html#ar... ), though I assume only the
             | most masochistic people enable it. I actually tried to
             | enable it once for some parsing code where I wanted to
             | enforce checked arithmetic, but it pointlessly triggered on
             | my Checked wrapper (as described in the previous paragraph)
             | so I ended up disabling it.
        
               | smj-edison wrote:
               | > Rust unfortunately picked the wrong default here
               | 
               | I partially disagree with this. Using Zig style
               | allocators doesn't really fit with Rust ergonomics, as it
               | would require pretty extensive lifetime annotations. With
               | no_std, you absolutely can roll your own allocation
               | styles, at the price of more manual lifetime annotations.
               | 
               | I do hope though that some library comes along that
               | allows for Zig style collections, with the associated
               | lifetimes... (It's been a bit painful rolling my own
               | local allocator for audio processing).
        
               | Arnavion wrote:
               | Explicit allocators do work with Rust, as evidenced by
               | them already working for libstd's types, as I said. The
               | mistake was to not have them from day one which has
               | caused most code to assume GlobalAlloc.
               | 
               | As long as the type is generic on the allocator, the
               | lifetimes of the allocator don't appear in the type. So
               | eg if your allocator is using a stack array in main then
               | your allocator happens to be backed by `&'a
               | [MaybeUninit<u8>]`, but things like Vec<T, A>
               | instantiated with A = YourAllocator<'a> don't need to be
               | concerned with 'a themselves.
               | 
               | Eg: https://play.rust-
               | lang.org/?version=nightly&mode=debug&editi...
               | do_something_with doesn't need to have any lifetimes from
               | the allocator.
               | 
               | If by Zig-style allocators you specifically mean type-
               | erased allocators, as a way to not have to parameterize
               | everything on A:Allocator, then yes the equivalent in
               | Rust would be a &'a dyn Allocator that has an infectious
               | 'a lifetime parameter instead. Given the choice between
               | an infectious type parameter and infectious lifetime
               | parameter I'd take the former.
        
               | smj-edison wrote:
               | Ah, my bad, I guess I've been misunderstanding how the
               | Allocator proposal works all along (I thought it was only
               | for 'static allocators, this actually makes a lot more
               | sense!).
               | 
               | I guess all that to say, I agree then that this should've
               | been in std from day one.
        
             | PhilipRoman wrote:
             | >what are they supposed to do when there is no memory left
             | 
             | Well on Linux they are apparently supposed to return memory
             | anyway and at some point in the future possibly SEGV your
             | process when you happen to dereference some unrelated
             | pointer.
        
         | dabinat wrote:
         | I wish Option and Result weren't exclusive. Sometimes a method
         | can return an error, no result or a valid result. Some crates
         | return an error for "no result", which feels wrong to me. My
         | solution is to wrap Result<Option>, but it still feels clunky.
         | 
         | I could of course create my own type for this, but then it
         | won't work with the ? operator.
        
           | vjerancrnjak wrote:
           | This sounds valid. Lookup in a db can be something or nothing
           | or error.
           | 
           | Just need a function that allows lifting option to result.
        
           | dicytea wrote:
           | > I could of course create my own type for this, but then it
           | won't work with the ? operator.
           | 
           | This is what the Try[^1] trait is aiming to solve, but it's
           | not stabilized yet.
           | 
           | [^1]: https://rust-lang.github.io/rfcs/3058-try-trait-v2.html
        
           | atoav wrote:
           | I think Result<Option> is the way to go. It describes
           | precisely that: was it Ok? if yes, was there a value?
           | 
           | I could imagine situations where an empty return value would
           | constitute an Error, but in 99% of cases returning None would
           | be better.
           | 
           | Result<Option> may feel clunky, but if I can give _one_
           | recommendation when it comes to Rust, is that you should not
           | value your own code-aesthetical feelings too much as it will
           | lead to a lot of pain in many cases -- work with the grain of
           | the language not against it even if the result does not
           | satisfy you. In this case I 'd highly recommend just using
           | Result<Option> and stop worrying about it.
           | 
           | You being able to compose/nest those base types and unwraping
           | or matching them in different sections of your code is a
           | strength not a weakness.
        
           | Arnavion wrote:
           | Result<Option> is the correct way to represent this, and if
           | you need further convincing, libstd uses it for the same
           | reason: https://doc.rust-
           | lang.org/stable/std/primitive.slice.html?se...
        
         | 90s_dev wrote:
         | I like so much about Rust.
         | 
         | But I hear compiling is too slow.
         | 
         | Is it a serious problem in practice?
        
           | zozbot234 wrote:
           | People who say "Rust compiling is so slow" have never
           | experienced what building large projects was like in the
           | mid-1990s or so. It's totally fine. Besides, there's also
           | https://xkcd.com/303/
        
             | kelnos wrote:
             | Not really relevant. The benchmark is how other language
             | toolchains perform _today_ , not what they failed to do 30
             | years ago. I don't think we'd find it acceptable to go back
             | to mid-'90s build times in other languages, so why should
             | we be ok with it with something like Rust?
        
             | creata wrote:
             | Or maybe they _have_ experienced what it was like and they
             | don 't want to go back.
        
           | mynameisash wrote:
           | It depends on where you're coming from. For me, Rust has
           | replaced a lot of Python code and a lot of C# code, so yes,
           | the Rust compilation is slow by comparison. However, it
           | really hasn't adversely affected (AFAICT) my/our iteration
           | speed on projects, and there are aspects of Rust that have
           | significantly sped things up (eg, compilation failures help
           | detect bugs before they make it into code that we're
           | testing/running).
           | 
           | Is it a serious problem? I'd say 'no', but YMMV.
        
           | Seattle3503 wrote:
           | Absolutely, the compile times are the biggest drawback IMO.
           | Everywhere I've been that built large systems in Rust
           | eventually ends up spending a good amount of dev time trying
           | to get CI/CD pipeline times to something sane.
           | 
           | Besides developer productivity it can be an issue when you
           | need a critical fix to go out quickly and your pipelines take
           | 60+ minutes.
        
             | lilyball wrote:
             | Don't use a single monolithic crate. Break your project up
             | into multiple crates. Not only does this help with compile
             | time (the individual crate compiles can be parallelized),
             | it also tends to help with API design as well.
        
               | mixmastamyk wrote:
               | It compiles different files separately, right?
               | 
               | With some exceptions for core data structures, it seems
               | that if you only modified a few files in a large project
               | the total compilation time would be quick no matter how
               | slow the compiler was.
        
               | conradludgate wrote:
               | Sorta. The "compilation unit" is a single crate, but
               | rustc is now also parallel, and LLVM can also be
               | configured to run in parallel IIRC.
               | 
               | Rust compile times have been improving over time as the
               | compiler gets incrementally rewritten and optimised.
        
               | Seattle3503 wrote:
               | Every project I've worked on used a workspace with many
               | crates. Generally that only gets you so far on large
               | projects.
        
             | nicoburns wrote:
             | If you have the money to throw at it, you can get a long
             | way optimising CI pipelines just by throwing faster
             | hardware at it. The sort of server you could rent for
             | ~$150/month might easily be ~5x faster than your typical
             | Github Actions hosted runner.
        
           | cmrdporcupine wrote:
           | I worked in the chromium C++ source tree for years and
           | compiling there was orders of magnitude slower than any Rust
           | source tree I've worked in so far.
           | 
           | Granted, there aren't any Rust projects that large yet, but I
           | feel like compilation speeds are something that can be worked
           | around with tooling (distributed build farms, etc.). C++'s
           | lack of safety and a proclivity for "use after free" errors
           | is harder to fix.
        
             | gpderetta wrote:
             | Are there rust projects that are within orders of magnitude
             | of Chromium?
        
           | kelnos wrote:
           | Compilation is indeed slow, and I do find it frustrating
           | sometimes, but all the other benefits Rust brings more than
           | make up for it in my book.
        
           | juliangmp wrote:
           | I can't speak for a bigger rust project, but my experience
           | with C++ (mostly with cmake) is so awful that I don't think
           | it can get any worse.
           | 
           | Like with any bigger C++ project there's like 3 build tools,
           | two different packaging systems and likely one or even
           | multiple code generators.
        
           | conradludgate wrote:
           | It is slow, and yes it is a problem, but given that typical
           | Rust code generally needs fewer full compiles to get working
           | tests (with more time spent active in the editor, with an
           | incremental compiler like Rust Analyzer) it usually balances
           | out.
           | 
           | Cargo also has good caching out of the box. While cargo is
           | not the best build system, it's an easy to use good system,
           | so you generally get good compile times for development when
           | you edit just one file. This is along made heavy use of with
           | docker workflows like cargo-chef.
        
           | throwaway76455 wrote:
           | Compile times are the reason why I'm sticking with C++,
           | especially with the recent progress on modules. I want people
           | with weaker computers to be able to build and contribute to
           | the software I write, and Rust is not the language for that.
        
         | cmrdporcupine wrote:
         | abseil's "StatusOr" is roughly like Rust's Result type, and is
         | what is used inside Google's C++ codebases (where exceptions
         | are mostly forbidden)
         | 
         | https://github.com/abseil/abseil-cpp/blob/master/absl/status...
        
         | fpoling wrote:
         | Result type still requires quite a few lines of boilerplate if
         | one needs to add custom data to it. And as a replacement of
         | exceptions with automatic stack trace attachment it is
         | relatively poor.
         | 
         | In any case I will take Rust Result over C++ mess at any time
         | especially given that we have two C++, one with exception
         | support and one without making code incompatible between two.
        
         | kccqzy wrote:
         | The Result type isn't really enough for fun and easy error
         | handling. I usually also need to reach for libraries like
         | anyhow https://docs.rs/anyhow/latest/anyhow/. Otherwise, you
         | still need to think about the different error types returned by
         | different libraries.
         | 
         | Back at Google, it was truly an error handling nirvana because
         | they had StatusOr which makes sure that the error type is just
         | Status, a standardized company-wide type that stills allows
         | significant custom errors that map to standardized error
         | categories.
        
         | 0x1ceb00da wrote:
         | Proper error handling is the biggest problem in a vast majority
         | of programs and rust makes that straightforward by providing a
         | framework that works really well. I hate the `?` shortcut
         | though. It's used horribly in many rust programs that I've seen
         | because the programmers just use it as a half assed replacement
         | for exceptions. Another gripe I have is that most library
         | authors don't document what errors are returned in what
         | situations and you're left making guesses or navigating through
         | the library code to figure this out.
        
         | bena wrote:
         | Ok, I'm at like 0 knowledge on the Rust side, so bear that in
         | mind. Also, to note that I'm genuinely curious about this
         | answer.
         | 
         | Why _can 't_ I return an integer on error? What's preventing me
         | from writing Rust like C++?
        
           | tczMUFlmoNk wrote:
           | You can write a Rust function that returns `i32` where a
           | negative value indicates an error case. Nothing in Rust
           | prevents you from doing that. But Rust does have facilities
           | that may offer a nicer way of solving your underlying
           | problem.
           | 
           | For instance, a common example of the "integer on error"
           | pattern in other languages is `array.index_of(element)`,
           | returning a non-negative index if found or a negative value
           | if not found. In Rust, the return type of
           | `Iterator::position` is instead `Option<usize>`. You can't
           | accidentally forget to check whether it's present. You could
           | still write your own `index_of(&self, element: &T) -> isize
           | /* negative if not found */` if that's your preference.
           | 
           | https://doc.rust-
           | lang.org/std/iter/trait.Iterator.html#metho...
        
           | bonzini wrote:
           | Nothing prevents you, you just get uglier code and more
           | possibility of confusion.
        
         | ryandrake wrote:
         | Error handling and propagation is one of those things I found
         | the most irritating and struggled[1] with the most as I learned
         | Rust, and to be honest, I'm still not sure I understand or like
         | Rust's way. Decades of C++ and Python has strongly biased me
         | towards the try/except pattern.
         | 
         | 1: https://news.ycombinator.com/item?id=41543183
        
           | skrtskrt wrote:
           | there are answers in the thread you linked that show how easy
           | and clean the error handling can be.
           | 
           | it can look just like a more-efficient `except` clauses with
           | all the safety, clarity, and convenience that enums provide.
           | 
           | Here's an example:
           | 
           | * Implementing an error type with enums: https://git.deuxfleu
           | rs.fr/Deuxfleurs/garage/src/branch/main/... * Which derives
           | from a more general error type with even more helpful enums: 
           | https://git.deuxfleurs.fr/Deuxfleurs/garage/src/branch/main/.
           | .. * then some straightforward handling of the error: https:/
           | /git.deuxfleurs.fr/Deuxfleurs/garage/src/branch/main/...
        
         | loeg wrote:
         | I work in a new-ish C++ codebase (mid-2021 origin) that uses a
         | Result-like type everywhere (folly::Expected, but you get
         | std::expected in C++23). We have a C pre-processor macro
         | instead of `?` (yes, it's a little less ergonomic, but it's
         | usable). It makes it relatively nice to work in.
         | 
         | That said, I'd prefer to be working in Rust. The C++ code we
         | call into can just raise exceptions anywhere implicitly; there
         | are a hell of a lot of things you can accidentally do wrong
         | without warning; class/method syntax is excessively verbose,
         | etc.
        
       | jpc0 wrote:
       | Amazing example of how easy it is to get sucked into the rust
       | love. Really sincerely these are really annoying parts of C++.
       | 
       | The conversation function is more language issue. I don't think
       | there is a simple way of creating a rust equivalent version
       | because C++ has implicit conversions. You could probably create a
       | C++ style turbofish though, parse<uint32_t>([your string]) and
       | have it throw or return std::expected. But you would need to
       | implement that yourself, unless there is some stdlib version I
       | don't know of.
       | 
       | Don't conflate language features with library features.
       | 
       | And -Wconversion might be useful for this but I haven't
       | personally tried it since what Matt is describing with explicit
       | types is the accepted best practice.
        
         | ujkiolp wrote:
         | meh, rust is still better cos it's friendlier
        
           | jpc0 wrote:
           | I don't disagree. Rust learnt a ton from C++.
           | 
           | I have my gripes with rust, more it's ecosystem and community
           | that the core language though. I won't ever say it's a worse
           | language than C++.
        
             | noelnh wrote:
             | Could you elaborate on those points, I'm genuinely curious?
             | So far, I have found the Rust community to be immensely
             | helpful, much more so than I experienced the C++ community.
             | Granted, that's quite some time ago and might be at least
             | partially caused by me asking fewer downright idiotic
             | questions. But still, I'm interested in hearing about your
             | experiences.
        
       | GardenLetter27 wrote:
       | It's a shame Rust doesn't have keyword arguments or named tuples
       | to make handling some of these things easier without Args/Options
       | structs boilerplate.
        
         | shpongled wrote:
         | Yep, I would love anonymous record types, ala StandardML/OCaml
        
         | frankus wrote:
         | I work all day in Swift (which makes you go out of your way to
         | omit argument labels) and I'm surprised they aren't more
         | common.
        
         | jsat wrote:
         | Had the same thought... It's backwards that any language isn't
         | using named parameters at this point.
        
           | Ygg2 wrote:
           | Named parameters do come with a large footgun. Renaming your
           | parameters is a breaking change.
           | 
           | Especially if you're coming from different langs.
        
       | morning-coffee wrote:
       | Reading "The Rust Book" sold me on Rust (after programming in C++
       | for over 20 years)
        
         | mixmastamyk wrote:
         | Am about finished, but several chapters near the end seriously
         | put me to sleep. Will try again some other day I suppose.
        
       | grumbel wrote:
       | There is '-Wconversion' to catch things like this. It will
       | however not trigger in this specific case since g++ assumes
       | converting 1000.0 to 1000 is ok due to no loss in precision.
       | 
       | Quantity(100) is counterproductive here, as that doesn't narrow
       | the type, it does the opposite, it casts whatever value is given
       | to the type, so even Quantity(100.5) will still work, while just
       | plain 100.5 would have given an error with '-Wconversion'.
        
         | Arnavion wrote:
         | The reason to introduce the Quantity wrapper is to not be able
         | to swap the quantity and price arguments.
        
         | b5n wrote:
         | > -Wconversion ... assumes converting 1000.0 to 1000 is ok due
         | to no loss in precision.
         | 
         | Additionally, `clang-tidy` catches this via `bugprone-
         | narrowing-conversions` and your linter will alert if properly
         | configured.
        
           | kelnos wrote:
           | My opinion is that if you need to run extra tools/linters in
           | order to catch basic errors, the language & its compiler are
           | not doing enough to protect me from correctness bugs.
           | 
           | I do run clippy on my Rust projects, but that's a matter of
           | style and readability, not correctness (for the most part!).
        
             | jpc0 wrote:
             | How much of what Rust the language checks is actually
             | linter checks implemented in the compiler?
             | 
             | Conversions may be fine and even useful in many cases, in
             | this case it isn't. Converting to std::variant or
             | std::optional are some of those cases that are really nice.
        
             | b5n wrote:
             | There's a bit more nuance here than 'basic errors', and
             | modern c compilers offer a lot of options _if you need to
             | use them_.
             | 
             | I appreciate that there are guardrails in a tool like rust,
             | I also appreciate that sharp tools like c exist, they both
             | have advantages.
        
               | Arnavion wrote:
               | To be clear, the only difference between Rust and C here
               | is whether the conversion happens by default or not. Rust
               | doesn't do the conversion by default but will let you do
               | it if you want to, with `as`.
               | 
               | There are also more type-safe conversion methods that
               | perform a more focused conversion. Eg a widening
               | conversion from i8 -> i16 can be done with .into(), a
               | narrowing conversion from i16 -> i8 can be done with
               | .try_into() (which returns a Result and forces you to
               | handle the overflow case), a signed to unsigned
               | reinterpretation like i64 -> u64 can be done with
               | .cast_unsigned(), and so on. Unlike `as` these have the
               | advantage that they stop compiling if the original value
               | changes type; eg if you refactor something and the i8 in
               | the first example becomes an i32, the i32 -> i16
               | conversion is no longer a widening conversion so the
               | `.into()` will fail to compile.
        
             | throwaway76455 wrote:
             | Setting up clang-tidy for your IDE isn't really any more
             | trouble than setting up a LSP. If you want the
             | compiler/linter/whatever to reject valid code to protect
             | you from yourself, there are tools you can use for that.
             | Dismissing them just because they aren't part of the
             | language (what, do you expect ISO C++ to enforce clang-tidy
             | usage?) is silly.
        
       | atemerev wrote:
       | Right. I attempted using Rust for trading-related code as well.
       | However, I failed to write a dynamically linked always sorted
       | order book where you can splice orders in the middle. It is just
       | too dynamic for Rust. Borrow checker killed me.
       | 
       | And don't get me started on dynamic graphs.
       | 
       | I would happily use Rust over C++ if it had all other
       | improvements but similar memory management. I am completely
       | unproductive with Rust model.
        
         | sunshowers wrote:
         | I apologize for the naive question, but that sounds like a
         | heap?
        
           | jpc0 wrote:
           | In my experience you need to approach this with vec or arrays
           | of some sort and pass indices around... "We have pointers at
           | home" behaviour. This is fine but coming from C++ it
           | definitely feels weird...
        
             | bigstrat2003 wrote:
             | Why not just use pointers? Rust has them, they aren't evil
             | or anything. If you need to make a data structure that
             | isn't feasible with references due to the borrow checker
             | (such as a linked list), there's absolutely nothing wrong
             | with using pointers.
        
               | atemerev wrote:
               | And it will look like this: https://rust-
               | unofficial.github.io/too-many-lists/sixth-final...
               | 
               | (filled with boilerplate, strange Rust idioms,
               | borrow_unchecked, phantomdata, and you still have to
               | manage lifetimes annotations).
        
             | sunshowers wrote:
             | I agree in general Rust makes you use arrays and indexes,
             | but heaps are traditionally implemented that way in any
             | language.
        
           | atemerev wrote:
           | We have to do arbitrary insertions/deletions from the middle,
           | many of them. I think it is more like BTreeMap, but we need
           | either sorting direction or rev(), and there were some
           | problems with both approaches I tried to solve, but
           | eventually gave up.
        
             | sunshowers wrote:
             | I see! The big issue I've run into with BTreeMap is that
             | you can't provide an external comparator. If comparisons
             | only require data that the keys already have, then the
             | Reverse wrapper [1] has worked well for me.
             | 
             | [1] https://doc.rust-lang.org/std/cmp/struct.Reverse.html
        
         | hacker_homie wrote:
         | I have run into similar issues trying to build real
         | applications. You end up spending more time arguing with the
         | borrow checker than writing code.
        
           | lytedev wrote:
           | I think this is true initially and Rust didn't "click" for me
           | for a long time.
           | 
           | But once you are _maintaining_ applications, man it really
           | does feel like absolute magic. It's amazing how worry-free it
           | feels in many respects.
           | 
           | Plus, once you do embrace it, become familiar, and start
           | forward-thinking about these things, especially in areas that
           | aren't every-nanosecond-counts performance-wise and can
           | simply `Arc<>` and `.clone()` where you need to, it is really
           | quite lovely and you do dramatically less fighting.
           | 
           | Rust is still missing a lot of features that other more-
           | modern languages have, no doubt, but it's been a great ride
           | in my experience.
        
             | skippyboxedhero wrote:
             | Using reference counts is a real issue.
             | 
             | The idea with Rust is that you get safety...not that you
             | get safety at the cost of performance. The language forces
             | you into paying a performance cost for using patterns when
             | it is relatively easy for a human to reason about safety
             | (imo).
             | 
             | You can use `unsafe` but you naturally ask yourself why I
             | am using Rust (not rational, but true). You can use
             | lifetimes but, personally, every time I have tried to use
             | them I haven't been able to indicate to the compiler that
             | my code is actually safe.
             | 
             | In particular, the protections for double-free and free
             | before use are extremely limiting, and it is possible to
             | reason about these particular bugs in other ways (i.e.
             | defer in Go and Zig) in a way that doesn't force you to
             | change the way you code.
             | 
             | Rust is good in many ways but the specific problem
             | mentioned at the top of this chain is a big issue. Just
             | saying: don't use this type of data structure unless you
             | pay performance cost isn't an actual solution to the
             | problem. The problem with Rust is that it tries to force
             | safety but doesn't have good ways for devs to tell the
             | compiler code is safe...that is a fundamental weakness.
             | 
             | I use Rust quite a bit, it isn't a terrible language and is
             | worth learning but these are big issues. I would have
             | reservations using the language in my own company, rather
             | than someone else's, and if I need to manage memory then I
             | would look elsewhere atm. Due to the size of the community,
             | it is very hard not to use Rust too (for example, Zig is
             | great...but no-one uses it).
        
               | lytedev wrote:
               | The idea with rust is that you _can_ have safety with no
               | performance cost if you need it, but depending on what
               | you're building, of course, that may imply extra work.
               | 
               | The pragmatism of Rust means that you can use reference
               | counting if it suits your use case.
               | 
               | Unsafe also doesn't mean throwing out the Rustiness of
               | Rust, but others have written more extensively about that
               | and I have no personal experience with it.
               | 
               | > The problem with Rust is that it tries to force safety
               | but doesn't have good ways for devs to tell the compiler
               | code is safe...that is a fundamental weakness.
               | 
               | My understanding is that this is the purpose of unsafe,
               | but again, I can't argue against these points from a
               | standpoint of experience, having stuck pretty strictly to
               | safe Rust.
               | 
               | Definitely agree that there are issues with the language,
               | no argument there! So do the maintainers!
               | 
               | > if I need to manage memory then I would look elsewhere
               | atm
               | 
               | Haha I have the exact opposite feeling! I wouldn't try to
               | manage memory any other way, and I'm guessing it's
               | because memory management is more intuitive and well
               | understood by you than by me. I'm lazy and very much like
               | having the compiler do the bulk of the thinking for me.
               | I'm also happy that Rust allows for folks like me to pay
               | a little performance cost and do things a little bit
               | easier while maintaining correctness. For the turbo-
               | coders out there that want the speed and the correctness,
               | Rust has the capability, but depending on your use case
               | (like linked lists) it can definitely be more difficult
               | to express correctness to the compiler.
        
               | skippyboxedhero wrote:
               | Agree, that is the purpose of unsafe but there is a
               | degree of irrationality there, which I am guilty of,
               | about using unsafe in Rust. I also worry about unsafe
               | leaking if I am using raw pointer on a struct...but
               | stdlib uses a lot of unsafe code, so I should be too.
               | 
               | I think the issue that people have is that they come into
               | Rust with the expectation that these problems are
               | actually solved. As I said, it would be nice if lifetimes
               | weren't so impossible to use.
               | 
               | The compiler isn't doing the thinking if you have to
               | change your code so the compiler is happy. The problem
               | with Rust is too much thinking: you try something,
               | compiler complains, what is the issue here, can i try
               | this, still complain, what about this, etc. There are
               | specific categories of bugs that Rust is trying to fix
               | that don't require the changes that Rust requires in
               | order to ensure correctness...if you use reference
               | counter, you can have more bugs.
        
         | 0x1ceb00da wrote:
         | > Borrow checker killed me.
         | 
         | You gotta get your timing right. Right hook followed by kidney
         | shot works every time.
        
         | kelnos wrote:
         | The nice thing is that you can always drop down to unsafe and
         | use raw pointers if your data structure is truly not suited to
         | Rust's ownership rules.
         | 
         | And while unsafe Rust does have some gotchas that vanilla
         | modern C++ does not, I would much rather have a 99% memory-safe
         | code base in Rust than a 100% "who knows" code base in C++.
        
           | atemerev wrote:
           | I have read the "too many linked lists" story and I think the
           | other commenters here are right; the less pointers the
           | better. Even with unsafe, there's just too much ceremony.
        
       | adamc wrote:
       | Coming from python (or Common Lisp, or...), I wasn't too
       | impressed. In Python I normally make args for any function with
       | more than a couple be keyword arguments, which guarantees that
       | you are aware of how the arguments are being mapped to inputs.
       | 
       | Even Rust's types aren't going to help you if two arguments
       | simply _have_ the same types.
        
         | rq1 wrote:
         | Just create dummy wrappers to make a type level distinction. A
         | Height and a a Width can be two separate types even if they're
         | only floats basically.
         | 
         | Or another (dummy) example transfer(accountA, accountB). Make
         | two types that wrap the same type but one being a TargetAccount
         | and the other SourceAccount.
         | 
         | Use the type system to help you, don't fight it.
        
           | jpc0 wrote:
           | Do you really want width and height or do you actually want
           | dimensions or size? Same with transfer, maybe you wanted a
           | transaction that gets executed. Worst case here use a builder
           | with explicit function names.
        
             | rq1 wrote:
             | I don't really understand your point there.
             | 
             | Sound type systems are equivalent to proof systems.
             | 
             | You can use them to design data structures where their mere
             | eventual existence guarantee the coherence and validity of
             | your program's state.
             | 
             | The basic example is "Fin n" that carries at compile time
             | the proof that you made the necessary bounds checks at
             | runtime or by construction that you never exceeded some
             | bound.
             | 
             | Some languages allow you to build entire type level state
             | machines! (eg. to represent these transactions and
             | transitions)
        
       | ModernMech wrote:
       | What sold me on Rust is that I'm a very bad programmer and I make
       | a lot of mistakes. Given C++, I can't help but hold things wrong
       | and shoot myself in the foot. My media C++ coding session is me
       | writing code, getting a segfault immediately, and then spending
       | time chasing down the reason for that happening, rinse and
       | repeat.
       | 
       | My median Rust coding session isn't much different, I also write
       | code that doesn't work, but it's caught by the compiler. Now,
       | most people call this "fighting with the borrow checker" but I
       | call it "avoiding segfaults before they happen" because when I
       | finally get through the compiler my code usually "just works".
       | It's that magical property Haskell has, Rust also has it to a
       | large extent.
       | 
       | So then what's different about Rust vs. C++? Well Rust actually
       | provides me a path to get to a working program whereas C++ just
       | leaves me with an error message and a treasure map.
       | 
       | What this means is that although I'm a bad programmer, Rust gives
       | me the support I need to build quite large programs on my own.
       | And that extends to the crate ecosystem as well, where they make
       | it very easy to build and link third party libraries, whereas
       | with C++ ld just tells you that it had a problem and you're left
       | on your own to figure out exactly what.
        
         | jpc0 wrote:
         | Using your media example since I have a decent amount of
         | experience there. Did you just use off the shelf libraries,
         | because effectively all the libraries are written in or expose
         | a C api. So now you not only need to deal with Rust, you need
         | to also deal with rust ffi.
         | 
         | There are some places I won't be excited to use rust, and media
         | heavy code is one of those places...
        
           | sophacles wrote:
           | Given that the second paragraph starts with "my median
           | rust..." i assume the "media C++" is actually a typo for
           | "median C++".
        
         | kanbankaren wrote:
         | > Given C++, I can't help but hold things wrong and shoot
         | myself
         | 
         | Give an example. I have been programming in C/C++ for close to
         | 30 years and the places where I worked had very strict
         | guidelines on C++ usage. We could count the number of times we
         | shot ourselves due to the language.
        
           | mb7733 wrote:
           | Isn't that their point though? They don't have 30 years of
           | C/C++ experience and a workplace with very strict guidelines.
           | They are just trying to write some code, and they run into
           | trouble on C++'s sharper edges.
        
       | hacker_homie wrote:
       | Wconversion And werror?
        
       | skywal_l wrote:
       | I love his videos on computerphile. One was just dropped a few
       | hours ago: https://www.youtube.com/watch?v=1su3lAh-k4o
        
       | jsat wrote:
       | I see an article about how strict typing is better, but what
       | would really be nice here is named parameters. I never want to go
       | back to anonymous parameters.
        
         | kelnos wrote:
         | Yes, this is one of the few things that I think was a big
         | mistake in Rust's language design. I used to do a lot of Scala,
         | and really liked named parameters there.
         | 
         | I suppose it could still be added in the future; there are
         | probably several syntax options that would be fully backward-
         | compatible, without even needing a new Rust edition.
        
         | codedokode wrote:
         | When there are 3-4 parameters it is too much trouble to write
         | the names.
        
           | noitpmeder wrote:
           | Not OP, but I imagine he's arguing for something like
           | python's optional named arguments.
        
           | bsder wrote:
           | > When there are 3-4 parameters it is too much trouble to
           | write the names.
           | 
           | Sorry, I don't agree.
           | 
           | First, code is read far more often than written. The few
           | seconds it takes to type out the arguments are paid again and
           | again each time you have to read it.
           | 
           | Second, this is one of the few things that autocomplete is
           | _really_ good at.
           | 
           | Third, almost everybody configures their IDE to display the
           | names _anyway_. So, you might as well put them into the
           | source code so people reading the code without an IDE gain
           | the benefit, too.
           | 
           | Finally, yes, they are redundant. _That 's the point_. If the
           | upstream changes something and renames the argument without
           | changing the type I probably want to review it anyway.
        
         | sophacles wrote:
         | Why? In 2025 we have tooling available for most every editor
         | that will annotate that information into the display without
         | needing them present in the file. When I autocomplete a
         | function name, all the parameters are there for me to fill in,
         | and annotated into the display afterwards. It seems like an
         | unnecessary step to reify it and force the bytes to be present
         | in the the saved file.
        
       | kasajian wrote:
       | This seems a big silly. This is not a language issue. You can
       | have a C++ library that does exactly all the things being shown
       | here so that the application developer doesn't worry about. There
       | would no C++ language features missing that would accomplish what
       | you're able to do on the Rust side.
       | 
       | So is this really a language comparison, or what libraries are
       | available for each language platform? If the latter, that's fine.
       | But let's be clear about what the issue is. It's not the
       | language, it's what libraries are included out of the box.
        
         | Etheryte wrote:
         | Just like language shapes the way we think and talk about
         | things, programming languages shape both what libraries are
         | written and how. You could write anything in anything so long
         | as it's Turing complete, but in real life we see clearly that
         | certain design decisions at the language level either advantage
         | or disadvantage certain types of solutions. Everyone could in
         | theory write C without any memory issues, but we all know how
         | that turns out in practice. The language matters.
        
         | lytedev wrote:
         | The core of this argument taken to its extreme kind of makes
         | the whole discussion pointless, right? All the languages can do
         | all the things, so why bother differentiating them?
         | 
         | To entertain the argument, though, it may not be a language
         | issue, but it certainly is a selling point for the language
         | (which to me indicates a "language issue") to me if the
         | language takes care of this "library" (or good defaults as I
         | might call them) for you with no additional effort -- including
         | tight compiler and tooling integration. That's not to say Rust
         | always has good defaults, but I think the author's point is
         | that if you compare them apples-to-oranges, it does highlight
         | the different focuses and feature sets.
         | 
         | I'm not a C++ expert by any stretch, so it's certainly a
         | possibility that such a library exists that makes Rust's type
         | system obsolete in this discussion around correctness, but I'm
         | not aware of it. And I would be incredibly surprised if it held
         | its ground in comparison to Rust in every respect!
        
         | sdenton4 wrote:
         | If the default is a loaded gun pointed at your foot, you're
         | going to end up with lots of people missing a foot. "just git
         | gud" isn't a solution.
        
           | cbsmith wrote:
           | That's an entirely different line of reasoning from the
           | article though, and "just git gud" isn't really the solution
           | here any more than it is to use Rust. There are facilities
           | for avoiding these problems that you don't have to learn how
           | to construct yourself _in either language_.
        
         | cbsmith wrote:
         | Yeah, I kept thinking, "doesn't mp-units basically address this
         | entirely"?
        
         | LinXitoW wrote:
         | Sure, you can emulate some of the features and hope that
         | everyone using your library is doing it "right". Just like you
         | could just use a dynamic language, tag every variable with a
         | type, and hope everyone using your library does the MANUAL work
         | of always doing it correct. Guess we don't need types either.
         | 
         | And while we're at it, why not use assembly? It's all just
         | "syntactic sugar" over bits, doesn't make any difference,
         | right?
        
       | codedokode wrote:
       | What about catching integer overflow? Free open-source languages
       | still cannot do it unlike they commercial competitors like Swift?
        
         | lytedev wrote:
         | I'm not sure if this is what you mean, exactly, but Rust indeed
         | catches this at compile time.
         | 
         | https://play.rust-lang.org/?version=stable&mode=debug&editio...
         | https://play.rust-lang.org/?version=stable&mode=debug&editio...
        
           | codedokode wrote:
           | I meant panic if during any addition (including in runtime)
           | an overflow occurs.
        
             | trealira wrote:
             | You can set a flag for that: https://doc.rust-
             | lang.org/rustc/codegen-options/index.html#o...
             | 
             | By default, they're on during debug mode and off in release
             | mode.
        
             | genrilz wrote:
             | If you obscure the implementation a bit, you can change
             | GP's example to a runtime overflow [0]. Note that by
             | default the checks will only occur when using the
             | unoptimized development profile. If you want your optimized
             | release build to also have checks, you can put 'overflow-
             | checks = true' in the '[profile.release]' section of your
             | cargo.toml file [1].                 [0]:
             | https://play.rust-lang.org/?version=stable&mode=debug&editi
             | on=2024&gist=847dc401e16fdff14ecf3724a3b15a93       [1]:
             | https://doc.rust-lang.org/cargo/reference/profiles.html
        
         | z_open wrote:
         | assembly catches integer overflow. You just need to check the
         | flag.
        
         | kelnos wrote:
         | Rust does have checked arithmetic operations (that return
         | Result), but you have to explicitly opt in to them, of course,
         | and they're not as ergonomic to use as regular arithmetic.
        
           | trealira wrote:
           | But, by default, normal arithmetic operations trap on
           | overflow in debug mode, although they wrap with optimizations
           | on.
        
       | mempko wrote:
       | Rust does seem to have a lot of nice features. My biggest blocker
       | for me going to Rust from C++ is that C++ has much better support
       | for generic programming. And now that Concepts have landed, I'm
       | not aware of any language that can compete in this area.
        
       | gbin wrote:
       | Interestingly in Rust I would immediately use an Enum for the
       | Order! Way more powerful semantically.
        
       | tumdum_ wrote:
       | The one thing that sold me on Rust was that I no longer had to
       | chase down heisenbugs caused by memory corruption.
        
       | thrwyexecbrain wrote:
       | The C++ code I write these days is actually pretty similar to
       | Rust: everything is explicit, lots of strong types, very simple
       | and clear lifetimes (arenas, pools), non-owning handles instead
       | of pointers. The only difference in practice is that the build
       | systems are different and that the Rust compiler is more helpful
       | (both in catching bugs and reporting errors). Neither a huge deal
       | if you have a proper build and testing setup and when everybody
       | on your team is pretty experienced.
       | 
       | By the way, using "atoi" in a code snippet in 2025 and
       | complaining that it is "not ideal" is, well, not ideal.
        
         | yodsanklai wrote:
         | > The C++ code I write these days
         | 
         | Meaning you're in a context where you have control on the C++
         | code you get to write. In my company, lots of people get to
         | update code without strict guidelines. As a result, the code is
         | going to be complex. I'd rather have a simpler and more
         | restrictive language and I'll always favor Rust projects to C++
         | ones.
        
           | bluGill wrote:
           | That is easy to say today, but I guarantee in 30 year Rust
           | will have rough edges too. People always want some new
           | feature and eventually one comes in that cannot be
           | accommodated nicely.
           | 
           | Of course it will probably not be as bad as C++, but still it
           | will be complex and people will be looking for a simpler
           | language.
        
             | timbit42 wrote:
             | How many rough edges will C++ have in another 30 years?
        
         | taylorallred wrote:
         | Cool that you're using areas/pools for lifetimes. Are you also
         | using custom data structures or stl (out of curiosity)?
        
         | kanbankaren wrote:
         | The C++ code I wrote 20 years ago also had strong typing and
         | clear lifetimes.
         | 
         | Modern C++ has reduced a lot of typing through type inference,
         | but otherwise the language is still strongly typed and
         | essentially the same.
        
       | mattgodbolt wrote:
       | Wow that guy, eh? He seems to turn up everywhere :D
        
       | antirez wrote:
       | You can have two arguments that are semantically as distinct and
       | important as quantity and price and be both integers, and if you
       | swap them is a big issue anyway. And you would be forced, if you
       | like this kind of programming, to create distinct types anyway.
       | But I never trust this kind of "toy" defensive programming. The
       | value comes from testing very well the code, from a rigorous
       | quality focus.
        
       | spyrja wrote:
       | To be fair, this sort of thing doesn't have to be so much worse
       | in C++ (yes, it would have been nice if it had been built into
       | the language itself to begin with). You just need a function to
       | do a back-and-forth conversion which then double-check the
       | results, ie:                 #include <exception>
       | #include <sstream>            template <typename From, typename
       | To>       void convert_safely_helper_(From const& value, To&
       | result) {         std::stringstream sst;         sst << value;
       | sst >> result;       }            // Doesn't throw, just fails
       | template <typename From, typename To>       bool
       | convert_safely(From const& value, To* result) {         From
       | check;         convert_safely_helper_(value, *result);
       | convert_safely_helper_(*result, check);         if (check !=
       | value) {           *result = To();           return false;
       | }         return true;       }            // Throws on error
       | template <typename To, typename From>       To
       | convert_safely(From const& value) {         To result;         if
       | (!convert_safely(value, &result))           throw
       | std::logic_error("invalid conversion");         return result;
       | }            #include <iostream>            template <typename
       | Buy, typename Quantity, typename Price>       void
       | sendOrder(const char* symbol, Buy buy, Quantity quantity, Price
       | price) {         std::cout << symbol << " " <<
       | convert_safely<bool>(buy) << " "                 <<
       | convert_safely<unsigned>(quantity) << " " <<
       | convert_safely<double>(price)                 << std::endl;
       | }            #define DISPLAY(expression)         \
       | std::cout << #expression << ": "; \         expression
       | template <typename Function>       void test(Function attempt) {
       | try {           attempt();         } catch (const std::exception&
       | error) {           std::cout << "[Error: " << error.what() << "]"
       | << std::endl;         }       }            int main(void) {
       | test([&] { DISPLAY(sendOrder("GOOG", true, 100, 1000.0)); });
       | test([&] { DISPLAY(sendOrder("GOOG", true, 100.0, 1000)); });
       | test([&] { DISPLAY(sendOrder("GOOG", true, -100, 1000)); });
       | test([&] { DISPLAY(sendOrder("GOOG", true, 100.5, 1000)); });
       | test([&] { DISPLAY(sendOrder("GOOG", 2, 100, 1000)); });       }
       | 
       | Output:                 sendOrder("GOOG", true, 100, 1000.0):
       | GOOG 1 100 1000       sendOrder("GOOG", true, 100.0, 1000): GOOG
       | 1 100 1000       sendOrder("GOOG", true, -100, 1000): GOOG 1
       | [Error: invalid conversion]       sendOrder("GOOG", true, 100.5,
       | 1000): GOOG 1 [Error: invalid conversion]       sendOrder("GOOG",
       | 2, 100, 1000): GOOG [Error: invalid conversion]
       | 
       | Rust of course leaves "less footguns laying around", but I still
       | prefer to use C++ if I have my druthers.
        
       | nyanpasu64 wrote:
       | The problem I've always had with unit type wrappers is you can't
       | convert between a &[f32] and a &[Amplitude<f32>] like you can
       | convert a single scalar value.
        
         | fvncc wrote:
         | There are libraries that help with these conversions. See e.g.:
         | https://docs.rs/bytemuck/latest/bytemuck/trait.TransparentWr...
        
       ___________________________________________________________________
       (page generated 2025-05-06 23:01 UTC)