[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)