[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 : 570 points
Date : 2025-05-06 17:51 UTC (1 days 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.
| wffurr wrote:
| That was fascinating reading and a graveyard of abandoned
| "better C" dialects: SaferC, Friendly C, Checked C, etc.
| 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!
| oconnor663 wrote:
| > The borrow checker rejects loads of sound programs - just
| read https://rust-unofficial.github.io/too-many-lists/
|
| It's important to be careful here: a lot (most? all?) of
| these rejections are programs that could be sound in a
| hypothetical Rust variant that didn't assert the
| unique/"noalias" nature of &mut reference, but are in fact
| unsound in actual Rust.
| 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.
| monkeyelite wrote:
| But unfortunately you will encounter async in libraries you
| want to use, so this approach is difficult
| 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.
| neonsunset wrote:
| > Microsoft was for years actively hostile toward attempts
| to run .NET programs on platforms other than Windows
|
| It's been 10 years. Even before that, no action was ever
| taken against Mono nor any restriction put or anything
| else. FWIW Swift shares a similar story, except Apple
| started to care only quite recently about it working
| anywhere else beyond their platforms.
|
| Oh, and by the way, you need to look at these metrics:
| https://dotnet.microsoft.com/en-us/platform/telemetry
|
| Maybe take off the conspiracy hat?
|
| > There are too many other great choices with broad
| platform and community support
|
| :) No, thanks, I'm good. You know why I stayed in .NET land
| and didn't switch to, say, Go? It's not that it's so good,
| it's because most alternatives are so bad in one or another
| area (often many at the same time).
| 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.
| uecker wrote:
| I have a plan for a safe C and also type-safe generic and
| bounds-checked containers. Here is some experimental (!)
| example: https://godbolt.org/z/G4ncoYjfW
|
| Except for some missing pieces, this is safe and I have a
| prototype based on GCC that would warn about any unsafe
| features. va_list can be safely used at least with format
| strings and for union I need an annotations. Life times are
| the bigger outstanding issue.
| trealira wrote:
| > The lack of proper C99 floating-point support, even in
| $CURRENTYEAR
|
| What do you mean? What's wrong with floating point numbers in
| C99?
| o11c wrote:
| I mean things like: compilers don't support the pragmas,
| and if the compiler can "see" constants they are often
| evaluated with the wrong rounding mode.
|
| I'm far from an expert but I've seen enough to know it's
| wrong.
| trealira wrote:
| Oh, I see. I didn't realize that happened.
| 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?
| uecker wrote:
| I have a plan for a safe subset of C which would just require a
| compiler to warn about certain constructs. I also have a
| proposal for a safe string type. I am not so sure about type
| conversions though, you get useful warnings already with
| existing compiler flags and you can solve the problem in the
| article already just by wrapping the types in structs.
| 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);
| dzaima wrote:
| Even that is somewhat bad, e.g. it means you miss "some_u64 =
| some_u32 * 8" losing bits due to promoting after the arith
| op, not before.
| renox wrote:
| I disagree: when you use floats, you implicitly accept the
| precision loss/roundings that comes with using floats.. IMHO
| int to float implicit conversion is fine as long as you have
| _explicit_ float to int conversion.
| 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.
| nerpaskteipntei wrote:
| Now there is also std::from_chars function
| jeroenhd wrote:
| std::from_chars will still accept "123abc". You have to
| manually check if all parts of the string have been
| consumed. On the other hand, " 123" is not accepted,
| because it starts with an invalid character, so the
| behaviour isn't "take the first acceptable number and parse
| that" either.
|
| To get the equivalent of Rust's if let
| Ok(x) = input.parse::<i32>() { println!("You
| entered {x}"); } else { eprintln!("You
| did not enter a number"); }
|
| you need something like: int x{};
| auto [ptr, ec] = std::from_chars(input.data(), input.data()
| + input.size(), x); if (ec == std::errc() && ptr
| == input.data() + input.size()) { std::cout
| << "You entered " << x << std::endl; } else {
| std::cerr << "You did not enter a valid number" <<
| std::endl; }
|
| I find the choice to always require a start and and end
| position, and not to provide a method that simply passes or
| fails, to be quite baffling. In C++26, they also added an
| automatic boolean conversion for from_chars' return type to
| indicate success, which considers "only consumed half the
| input from the start" to be a success.
|
| Maybe I'm weird for mostly writing code that does
| straightforward input-to-number conversions and not partial
| string parsers, but I have yet to see a good alternative
| for Rust's parse().
| roelschroeven wrote:
| I guess there's a place for functions that extract or
| parse partially, but IMO there is a real need for an
| actual conversion function like Rust's parse() or
| Python's int() or float(). I think it's a real shame C++
| (and C as well) only offers the first and not the second.
| 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.
| thesuperbigfrog wrote:
| The idiomatic Rust way to do conversions is using the From
| and TryFrom traits:
|
| https://doc.rust-lang.org/stable/rust-by-
| example/conversion/...
|
| https://doc.rust-lang.org/stable/rust-by-
| example/conversion/...
|
| If the conversion will always succeed (for example an 8-bit
| unsigned integer to a 32-bit unsigned integer), the From
| trait would be used to allow the conversion to feel implicit.
|
| If the conversion could fail (for example a 32-bit unsigned
| integer to an 8-bit unsigned integer), the TryFrom trait
| would be used so that an appropriate error could be returned
| in the Result.
|
| These traits prevent errors when converting between types and
| clearly mark conversions that might fail since they return
| Result instead of the output type.
| im3w1l wrote:
| Thanks yeah I probably misremembered or misunderstood.
| 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.
| johnisgood wrote:
| I scratch my head when people try to justify Rust's
| implementation of X. It looks absolutely horrendous, IMO.
|
| I would rather have what OCaml has:
| https://ocaml.org/docs/error-handling.
| Cloudef wrote:
| You can use anyhow, but yeah zig generally does errors better
| IMO
| ziml77 wrote:
| Errors are where I find zig severely lacking. They can't
| carry context. Like if you're parsing a JSON file and it
| fails, you can know that it failed but not where it failed
| within the file. Their solution in the standard library for
| cases like this was to handle printing to stderr
| internally, but that is incredibly hacky.
| 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.
| dwattttt wrote:
| I find myself working through a hierarchy of error handling
| maturity as a project matures.
|
| Initial proof of concepts just get panics (usually with a
| message).
|
| Then functions start to be fallible, by adding anyhow &
| considering all errors to still be fatal, but at least
| nicely report backtraces (or other things! context doesn't
| have to just be a message)
|
| Then if a project is around long enough, swap anyhow to
| thiserror to express what failure modes a function has.
| 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.
| josephg wrote:
| I agree with you, but it's definitely inconvenient. Result
| also doesn't capture a stack trace. I spent a long time
| tracking down bugs in some custom binary parsing code
| awhile ago because I had no idea which stack trace my
| Result::Err's were coming from. I could have switched to
| another library - but I didn't want to inflict extra
| dependencies on people using my crate.
|
| As you say, it's not "batteries included". I think that's a
| fine answer given rust is a systems language. But in
| application code I want batteries to be included. I don't
| want to need to opt in to the right 3rd party library.
|
| I think rust could learn a thing or two from Swift here.
| Swift's equivalent is better thought through. Result is
| more part of the language, and less just bolted on:
|
| https://docs.swift.org/swift-book/documentation/the-swift-
| pr...
| 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?
| epage wrote:
| Those are generally used as asserts, not control flow / error
| handling.
| 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).
| throwaway2037 wrote:
| If I had to guess, that idea came from Andrei
| Alexandrescu.
| loeg wrote:
| Bingo.
|
| https://github.com/facebook/folly/commit/4d932ecc53c7a722
| 671...
| 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.
| jchw wrote:
| The reason it works this way is there's legitimately no
| easy way around it. You're not guaranteed a reasonable
| zero value for any type, so you can't do the slightly
| better Go thing (defined behavior but still wrong... Not
| great.) and you certainly can't do the Rust thing,
| because... There's no pattern matching. You can't
| conditionally enter a branch based on the presence of a
| value.
|
| There really is no reasonable workaround here, the
| language needs to be amended to make this safe and
| ergonomic. They tried to be cheeky with some of the other
| APIs, like std::variant, but really the best you can do
| is chuck the conditional branch into a lambda (or other
| function-based implementation of visitors) and the
| ergonomics of that are pretty unimpressive.
|
| Edit: but maybe fortune will change in the future, for
| anyone who still cares:
|
| https://www.open-
| std.org/jtc1/sc22/wg21/docs/papers/2024/p26...
| CJefferson wrote:
| You could assert. You could throw. I can't understand
| how, this modern age where so many programs end up
| getting hacked, that introducing more UB seems like a
| good idea.
|
| This is one odd the major reasons I switched to rust,
| just to escape spending my whole life worrying about bugs
| caused by UB.
| jchw wrote:
| Assertions are debug-only. Exceptions are usually not
| guaranteed to be available and much of the standard
| library doesn't require them. You could std::abort, and
| that's about it.
|
| I think the issue is that this just isn't particularly
| good either. If you do that, then you can't catch it like
| an exception, but you also can't statically verify that
| it won't happen.
|
| C++ needs less of both undefined behavior _and_ runtime
| errors. It needs more compile-time errors. It _needs_
| pattern matching.
| CJefferson wrote:
| I agree these things would be better, but I don't
| understand how anyone can think UB is better than abort.
|
| (Going to moan for a bit, and I realise you aren't
| responsible for the C++ standards mess!)
|
| I have been hearing for about... 20 years now that UB
| gives compilers and tools the freedom to produce any
| error catching they like, but all it seems to have done
| in the main is give them the freedom to produce hard to
| debug crash code.
|
| You can of course usually turn on some kind of "debug
| mode" in some compilers, but why not just enforce that as
| standard? Compilers would still be free to add a
| "standards non-compliant" go fast mode if they like.
| affyboi wrote:
| > but why not just enforce that as standard
|
| I don't think people want that as standard. The whole
| point of using C++ tends to be because you can do
| whatever you need to for the sake of performance. The
| language is also heavily driven by firms that need
| extreme performance (because otherwise why not use a
| higher level language)
|
| There are knobs like stdlib assertions and ubsan, but
| that's opt-in because there's a cost to it. Part of it is
| also the commitment to backwards compatibility and code
| that compiled before should generally compile now (though
| there are exceptions to that unofficial rule).
| jchw wrote:
| There does not need to be an additional cost for this.
|
| Most users will do this:
|
| 1. Check if there is a value
|
| 2. Get the value
|
| There is nothing theoretically preventing the compiler
| from enforcing that step 1 happens before step 2,
| especially if the compiler is able to combine the control
| flow branch with the process of conditionally getting the
| value. The practical issue is that there's no way to
| express this in C++ at all. The best you can do is the
| visitor pattern, which has horrible ergonomics and you
| can only hope it doesn't cause worse code generation too.
|
| Some users want to do this:
|
| 1. Grab the value without checking to see if it's valid.
| They are sure it will be valid and can't or don't want to
| eat the cost of checking.
|
| There is nothing theoretically preventing this from
| existing as a separate method.
|
| I'm not a rust fanboy (seriously, check my GitHub @jchv
| and look at how much Rust I write, it's approximately
| zero) but Rust has this solved six ways through Sunday.
| It can do _both_ of these cases just fine. The only
| caveat is that you have to wrap the latter case in an
| unsafe, but either way, you 're not eating any costs you
| don't want to.
|
| C++ can do this too. C++ has an active proposal for a
| feature that can fix this problem and make much more
| ergonomic std::variant possible, too.
|
| https://www.open-
| std.org/jtc1/sc22/wg21/docs/papers/2024/p26...
|
| Of course, this is one single microcosm in the storied
| history of C++ failing to adequately address the problem
| of undefined behavior proliferating the language, so I
| don't have high hopes.
| bluGill wrote:
| A lot UB is things you wouldn't do anyway. While it is
| possible to define divide by zero or integer overflow,
| what does it mean. If you code does either of those
| things you have a bug in your code (a few encryption
| algorithms depend on specific overflow behavior - if your
| language promises that same behavior it is useful).
|
| Since CPUs handle such things differently whatever you
| define to happen means that the compiler as to insert a
| if to check on any CPU that doesn't work how you define
| it - all for something that you probably are not doing.
| The cost is too high in a tight loop when you know this
| won't even happen (but the compiler does not).
|
| No
| jchw wrote:
| This is a bad answer too, IMO.
|
| I think there is a solid case for the existence of
| undefined behavior; even Rust has it, it's nothing absurd
| in concept, and you do describe some reasoning for why it
| should probably exist.
|
| However, and here's the real kicker, _it really does not
| need to exist for this case_. The real reason it exists
| for this case is due to increasingly glaring deficiencies
| in the C++ language, namely, again, the lack of any form
| of pattern matching for control flow. Because of this,
| there 's no way for a library author, including the STL
| itself, to actually handle this situation succinctly.
|
| Undefined behavior indeed _should_ exist, but not for
| common cases like "oops, I didn't check to see if there
| was actually a value here before accessing it." Armed
| with a moderately sufficient programming language, the
| compiler can handle that. Undefined behavior should be
| more like "I know you (the compiler) can't know this is
| safe, but I already know that this unsafe thing I'm doing
| is actually correct, so don't generate safeguards for me;
| let what happens, happen." This is what modern
| programming languages aim to do. C++ does that for shit
| like basic arithmetic, and that's why we get to have the
| same fucking CVEs for 20+ years, over and over in an
| endless loop. "Just get better at programming" is a nice
| platitude, but it doesn't work. Even if it was possible
| for me to become absolutely perfect and simply just never
| make any mistakes ever (lol) it doesn't matter because
| there's no chance in hell you'll ever manage that across
| a meaningful segment of the industry, including the parts
| of the industry you depend on (like your OS, or
| cryptography libraries, and so on...)
|
| And I don't think the issue is that the STL "doesn't
| care" about the possibility that you might accidentally
| do something that makes no sense. Seriously, take a look
| at the design of std::variant: it is pretty obvious that
| they wanted to design a "safe" union. In fact, what the
| hell would the point of designing another _unsafe_ union
| be in the first place? So they go the other route.
| std::variant has getters that throw exceptions on bad
| accesses instead of undefined behavior. This is literally
| the exact same type of problem that std::expected has.
| std::expected is essentially just a special case of a
| type-safe union with exactly two possible values, an
| expected and unexpected value (though since std::variant
| is tagged off of types, there is the obvious caveat that
| std::expected isn 't _quite_ a subset of std::variant,
| since std::expected could have the same type for both the
| expected and unexpected values.)
|
| So, what's wrong? Here's what's wrong. C++ Modules were
| first proposed in 2004[1]. C++20 finally introduced a
| version of modules and lo and behold, they mostly suck[2]
| and mostly aren't used by anyone (Seriously: they're not
| even fully supported by CMake _right now_.) Andrei
| Alexandrescu has been talking about std::expected since
| _at least_ 2018[3] and it _just now_ finally managed to
| get into the standard in C++23, and god knows if anyone
| will ever actually use it. And finally, pattern matching
| was originally proposed by none other than Bjarne himself
| (and Gabriel Dos Reis) in 2019[4] and who knows when it
| will make it into the standard. (I hope soon enough so it
| can be adopted before the heat death of the Universe, but
| I think that 's only if we get exceptionally lucky.)
|
| Now I'm not saying that adding new and bold features to a
| language as old and complex as C++ could possibly ever be
| easy or quick, but the pace that C++ evolves at is
| sometimes so slow that it's hard to come to any
| conclusion other than that the C++ standard and the
| process behind it is simply broken. It's just that
| simple. I don't care what changes it would take to get
| things moving more efficiently: it's not my job to figure
| that out. It doesn't matter why, either. The point is, at
| the end of the day, it can't take _this long_ for
| features to land just for them to wind up not even being
| very good, and there are plenty of other programming
| languages that have done better with less resources.
|
| I think it's obvious at this point that C++ will never
| get a handle on all of the undefined behavior; they've
| just introduced far too much undefined behavior all
| throughout the language and standard library in ways that
| are going to be hard to fix, especially while maintaining
| backwards compatibility. It should go without saying that
| a meaningful "safe" subset of C++ that can guarantee
| safety from memory errors, concurrency errors or most
| types of undefined behavior is simply never going to
| happen. Ever. It's not that it isn't possible to do, or
| that it's not worth doing, it's that C++ won't. (And yes,
| I'm aware of the attempts at this; they didn't work.)
|
| The uncontrolled proliferation of undefined behavior is
| ultimately what is killing C++, and a lot of very trivial
| cases _could_ be avoided, if only the language was
| capable of it, but it 's not.
|
| [1]: https://www.open-
| std.org/jtc1/sc22/wg21/docs/papers/2004/n17...
|
| [2]: https://vector-of-bool.github.io/2019/01/27/modules-
| doa.html
|
| [3]: https://www.youtube.com/watch?v=PH4WBuE1BHI
|
| [4]: https://www.open-
| std.org/jtc1/sc22/wg21/docs/papers/2019/p13...
| bluGill wrote:
| I cannot follow your rant... I'll do my best to respond,
| but I'm probably not understanding something.
|
| Divide by zero must be undefined behavior in any
| performant language. On x86 you either have a if before
| running the divide (which of course in some cases the
| compiler can optimize out, but only if it can determine
| the value is not zero); or you the CPU will trap into the
| OS - different OSes handle this in different ways, but
| most not in a while that makes it possible to figure out
| where you were and thus do something about it. This just
| came up in the C++ std-proposals mailing list in the past
| couple weeks.
|
| AFAIK all common CPUs have the same behavior on integer
| overflow (two-complement). However in almost all cases
| (again, some encryption code is an exception) that
| behavior is useless to real code and so if it happens
| your code has a bug either way. Thus we may as well let
| compilers optimize assuming it cannot happen as it if it
| does you have a bug no matter what we define it as. (C++
| is used on CPUs that are not two-complement as well, but
| we could call this implementation defined or unspecified,
| but it doesn't change that you have a bug if you invoke
| it.)
|
| For std::expected - new benchmarks are proving in the
| real world, and with optimized exception handlers that
| exceptions are faster in the real world than systems that
| use things like expected. Microbenchmarks that show
| exceptions are slower are easy to create, but real world
| exceptions that unwind more than a couple function calls
| show different results.
|
| As for modules, support is finally here and early
| adopters are using it. The road was long, but it is
| finally proving it worked.
|
| Long roads are a good thing. C++ has avoided a lot of bad
| designs by spending a lot of time thinking about problems
| about things for a long time. Details often matter and
| move fast languages tend to run into problems when
| something doesn't work as well as they want. I'm glad C++
| standardization is slow - it already is a mess without
| add more half backed features to the language.
| jchw wrote:
| > Divide by zero must be undefined behavior in any
| performant language. On x86 you either have a if before
| running the divide (which of course in some cases the
| compiler can optimize out, but only if it can determine
| the value is not zero); or you the CPU will trap into the
| OS - different OSes handle this in different ways, but
| most not in a while that makes it possible to figure out
| where you were and thus do something about it. This just
| came up in the C++ std-proposals mailing list in the past
| couple weeks.
|
| I mean look, I already agree that it's not necessarily
| unreasonable to have undefined behavior, but this
| statement is purely false. You absolutely can eat your
| cake and have it too. Here's how:
|
| - Split the operation in two: safe, checked division, and
| fast, unchecked division.
|
| - OR, Stronger typing; a "not-zero" type that represents
| a numeric type where you can guarantee the value isn't
| zero. If you can't eat the cost of runtime checks, you
| can unsafely cast to this.
|
| I think the former is a good fit for C++.
|
| C++ does _not_ have to do what Rust does, but for sake of
| argument, let 's talk about it. What Rust does here is
| simple, it just defines divide-by-zero to panic. How?
| Multiple ways:
|
| - If it knows statically it will panic, that's a
| compilation error.
|
| - If it knows statically it can not be zero, it generates
| unchecked division.
|
| - If it does not know statically, it generates a branch.
| (Though it is free to implement this however it wants;
| could be done using CPU exceptions/traps if they wanted.)
|
| What if you really do need "unsafe" division? Well, that
| _is_ possible, with unchecked_div. Most people do not
| need unchecked_div. If you think you do but you haven 't
| benchmarked yet, _you do not_. It doesn 't get any
| simpler than that. This is _especially_ the case if you
| 're working on modern CPUs with massive pipelines and
| branch predictors; a lot of these checks wind up having a
| very close to zero cost.
|
| > AFAIK all common CPUs have the same behavior on integer
| overflow (two-complement). However in almost all cases
| (again, some encryption code is an exception) that
| behavior is useless to real code and so if it happens
| your code has a bug either way. Thus we may as well let
| compilers optimize assuming it cannot happen as it if it
| does you have a bug no matter what we define it as. (C++
| is used on CPUs that are not two-complement as well, but
| we could call this implementation defined or unspecified,
| but it doesn't change that you have a bug if you invoke
| it.)
|
| It would be better to just do checked arithmetic by
| default; the compiler can often statically eliminate the
| checks, you can opt out of them if you need performance
| and know what you're doing, and the cost of checks is
| unlikely to be noticed on modern processors.
|
| It doesn't matter that this _usually_ isn 't a problem.
| It only has to be a problem once to cause a serious CVE.
| (Spoiler alert: it has happened more than once.)
|
| > For std::expected - new benchmarks are proving in the
| real world, and with optimized exception handlers that
| exceptions are faster in the real world than systems that
| use things like expected. Microbenchmarks that show
| exceptions are slower are easy to create, but real world
| exceptions that unwind more than a couple function calls
| show different results.
|
| You can always use stack unwinding or exceptions if you
| want to; that's also present in Rust too, in the form of
| panic. The nice thing about something like std::expected
| is that it theoretically can bridge the gap between code
| that uses exceptions and code that doesn't: you can catch
| an exception and stuff it into the `e` of an
| std::expected value, or you can take the `e` value of an
| std::expected and throw it. In theory this should not
| have much higher cost than simply throwing.
|
| > As for modules, support is finally here and early
| adopters are using it. The road was long, but it is
| finally proving it worked.
|
| Last I was at Google, they seemed to have ruled out C++
| modules because as-designed they are basically guaranteed
| to make compilation times worse.
|
| For CMake, you can't really rely on C++ Modules. Firstly,
| the Makefile generator which is default on most platforms
| literally does not and as far as I know _will_ not
| support C++ Modules. Secondly, it doesn 't support header
| units or importing the STL as modules. For all intents
| and purposes, it would be difficult to even use this for
| anything.
|
| For Bazel, there is no C++ Modules support to my
| knowledge.
|
| While fact-checking myself, I found this handy website:
|
| https://arewemodulesyet.org/tools/
|
| ...which shows CMake as supporting modules, green check
| mark, _no notes!_ So that really makes me wonder what
| value you can place on the other green checkmarks.
|
| > Long roads are a good thing. C++ has avoided a lot of
| bad designs by spending a lot of time thinking about
| problems about things for a long time. Details often
| matter and move fast languages tend to run into problems
| when something doesn't work as well as they want. I'm
| glad C++ standardization is slow - it already is a mess
| without add more half backed features to the language.
|
| I'm glad you are happy with the C++ standardization
| process. I'm not. Not only do things take many years,
| they're also half-baked at the end of the process. You're
| right that C++ still winds up with a huge mess of half-
| baked features even with as slow as the development
| process is, and modules are a great example of that.
|
| The true answer is that the C++ committee is a fucking
| mess. I won't sit here and try to make that argument;
| plenty of people have done a damningly good job at it
| better than I ever could. What I will say is that C faces
| a lot of similar problems to C++ and somehow still
| manages to make better progress anyways. The failure of
| the C++ standard committee could be told in many
| different ways. A good relatively recent example is the
| success of the #embed directive[1]. Of course, the reason
| why it was successful was because it was added to C
| instead of C++.
|
| Why can't C++ do that? I dunno. Ask Bjarne and friends.
|
| [1]: https://thephd.dev/finally-embed-in-c23
| bluGill wrote:
| Google has a odd C++ style guide that rules out a lot of
| useful things for their own reasons.
|
| There is no reason why make could not work with modules
| if someone wanted to go through the effort. The CMake
| people have even outlined what needs to be done. Ninja is
| so much nicer that you should switch anyway - I did more
| than 10 years ago.
| jchw wrote:
| I do use Ninja when I use CMake, but honestly that mostly
| comes down to the fact that the Makefiles generated by
| CMake are horrifically slow. I don't particularly love
| CMake, I only use it because the C++ ecosystem really has
| nothing better to offer. (And there's no chance I'm going
| to redistribute a project that can't build with the
| Makefile generator, at least unless and until Ninja is
| default.)
|
| Anyway, the Google C++ style guide has nothing to do with
| why C++ modules aren't and won't be used at Google, it's
| because as-implemented modules are not an obvious win.
| They can theoretically improve performance, but they can
| and do also make some cases worse than before.
|
| I don't think most organizations will adopt modules at
| this rate. I suspect the early adopters will wind up
| being the only adopters for this one.
| patrick451 wrote:
| > What if you really do need "unsafe" division? Well,
| that is possible, with unchecked_div. Most people do not
| need unchecked_div. If you think you do but you haven't
| benchmarked yet, you do not. It doesn't get any simpler
| than that.
|
| This attitude is why modern software is dogshit slow.
| People make this "if you haven't benchmarked, it doesn't
| matter" argument thousands of times and the result is
| that every program I run is slower than molasses in
| Siberia. I don't care about "safety" at the expense of
| performance.
| jchw wrote:
| > This attitude is why modern software is dogshit slow.
|
| Bullshit. Here's my proof: We don't even _do_ this. There
| 's a ton of software that isn't safe from undefined
| behavior and it's still slow as shit.
|
| > People make this "if you haven't benchmarked, it
| doesn't matter" argument thousands of times and the
| result is that every program I run is slower than
| molasses in Siberia. I don't care about "safety" at the
| expense of performance.
|
| If you can't imagine a world where there's nuance between
| "we should occasionally eat 0.5-1ns on checking an unsafe
| division" and "We should ship an entire web browser with
| every text editor and chat app" the problem is with you.
| If you want your software to be fast, it _has_ to be
| benchmarked, just like if you want it to be stable, it
| _has_ to be tested. There 's really no exceptions here,
| you can't just guess these things.
| nayuki wrote:
| I agree very much with what you wrote.
|
| > the lack of any form of pattern matching for control
| flow
|
| Growing features after the fact is hard. Look at the
| monumental effort to get generics into Go. Look at how
| even though Python 3.10 introduced the match statement,
| it is a statement and not an expression - you can't write
| `x = match ...`, unlike Rust and Java 14. So it doesn't
| surprise me that C++ struggles with this.
|
| > Undefined behavior indeed should exist
|
| Agreed. Rust throws up its hands in narrow cases (
| https://doc.rust-lang.org/reference/behavior-considered-
| unde... ), and even Java says that calling Thread.stop()
| and forcing monitor unlocks can lead to corrupted data
| and UB.
|
| > but not for common cases like
|
| Yes, C/C++ have far, far too many UB cases. Even down to
| idiotically simple things like "failing to end a source
| file with newline". C and C++ have liberally sprinkled UB
| as a cop-out like no other language.
|
| > C++ does that for shit like basic arithmetic
|
| I spent an unhealthy amount of time understanding the
| rules of integer types and arithmetic in C/C++. Other
| languages like Rust are as capable without the extreme
| mental complexity. https://www.nayuki.io/page/summary-of-
| c-cpp-integer-rules
|
| Oh and, `(uint16_t)0xFFFF * (uint16_t)0xFFFF` will cause
| a _signed_ 32-bit integer overflow on most platforms, and
| that is UB and will eat your baby. Scared yet? C /C++
| rules are batshit insane.
|
| > "Just get better at programming" is a nice platitude,
| but it doesn't work.
|
| Correct. Far too often, I hear a conversation like "C/C++
| have too many UB, why can't we make it safer?" "Just
| learn to write better code, dumbass". No, literal decades
| of watching the industry tells us that the same mistakes
| keep happening over and over again. The evidence is
| overwhelming that the languages need to change, not the
| programmers.
|
| > it's obvious at this point that C++ will never get a
| handle on all of the undefined behavior; they've just
| introduced far too much undefined behavior all throughout
| the language and standard library
|
| True.
|
| > in ways that are going to be hard to fix, especially
| while maintaining backwards compatibility
|
| Technically not true. Specifying undefined behavior is
| easy, and this has already been done in many ways. For
| example, -fwrapv makes signed overflow defined to wrap
| around. For example, you could zero-initialize every
| local variable and change malloc() to behave like
| calloc(), so that reading uninitialized memory always
| returns zero. And because the previous behavior was
| undefined anyway, literally any substitute behavior is
| valid.
|
| The problem isn't maintaining backward compatibility,
| it's maintaining performance compatibility. Allegedly,
| undefined behavior allows the compiler to optimize out
| redundant arithmetic, redundant null checks, etc. I
| believe this is what stops the standards committees from
| simply defining some kind of reasonable behavior for what
| is currently considered UB.
|
| > a meaningful "safe" subset of C++ that can guarantee
| safety from memory errors, concurrency errors or most
| types of undefined behavior is simply never going to
| happen
|
| I think it has already happened. Fil-C seems like a
| capable approach to transpile C/C++ and add a managed
| runtime - and without much overhead.
| https://github.com/pizlonator/llvm-project-
| deluge/blob/delug...
|
| > The uncontrolled proliferation of undefined behavior is
| ultimately what is killing C++
|
| It's death by a thousand cuts, and it hurts language
| learners the most. I can write C and C++ code without UB,
| but it took me a long time to get there - with a lot of
| education and practice. And UB-free code can be awkward
| to write. The worst part of it is that the knowledge is
| very C/C++-specific and is useless in other languages
| because they don't have those classes of UB to begin
| with.
|
| I dabbled in C++ programming for about 10 years before I
| discovered Rust. Once I wrote my first few Rust programs,
| I was hooked. Suddenly, I stopped worrying about all the
| stupid complexities and language minutiae of C++. Rust
| just made sense out of the box. It provided far fewer
| ways to do things ( https://www.nayuki.io/page/near-
| duplicate-features-of-cplusp... ), and the easy way is
| usually the safe and correct way.
|
| To me, Rust is C++ done right. It has the expressive
| power and compactness of C++ but almost none of the
| downsides. It is the true intellectual successor to C++.
| C++ needs to hurry up and die already.
| lallysingh wrote:
| Culturally, I think C++ has a policy of "there's no
| single right answer." Which leads to there being no wrong
| answers. We just need more answers so everyone's happy.
| Which is worse.
| mgaunard wrote:
| Of course you can do the Rust thing, it's just taking a
| function object.
| dietr1ch wrote:
| `StatusOr<T>::operator _` there is akin to `Result <T,
| _>::unwrap()`. On C++ unwrapping looks like dereferencing
| a pointer which is scary and likely UB already.
|
| But as you learn to work with StatusOr you'll end up just
| using just ASSIGN_OR_RETURN everytime and dereferencing
| remains scary. I guess the complaint is that C++ won't
| guarantee that the execution will stop, but that's the
| C++ way after you drop all safety checks in
| `StatusOr::operator_` to gain performance.
| d_tr wrote:
| C++ carries so much on its back and this makes its evolution
| over the past decade even more impressive.
| pjmlp wrote:
| Yes, people keep forgeting C++ was made public with CFront
| 2.0 back in 1989, 36 years of backwards compatibility, to
| certain extent.
| bluGill wrote:
| C++ is C compatible so more than 50 years of backward
| compatibility. Even today the vast majority of C programs
| can be compiled as C++ and they just work. Often such
| programs run faster because C++ a few additions that the
| compiler can use to optimize better, in practice C
| programs generally mean the stronger rules anyway (but of
| course when they don't the program is wrong).
| KerrAvon wrote:
| <pedantry corner>CFront was never compatible with K&R C
| to the best of my knowledge, so the actual start date
| would be whenever C89-style code in widespread use; I'm
| not sure how long before 1989 that was.
| 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.
| tialaramex wrote:
| You can't really do Try (which is that operator's name in
| Rust) because C++ lacks a ControlFlow type which is how Try
| reflects the type's decision about whether to exit early.
|
| You can imitate the beginner experience of the ? operator
| as magically handling trivial error cases by "just knowing"
| what should happen, but it's not the same thing as the
| current Try feature.
|
| Barry Revzin has a proposal for some future C++ (lets say
| C++ 29) to introduce statement expressions, the syntax is
| very ugly even by C++ standards but it would semantically
| solve the problem you had.
| jll29 wrote:
| > 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.
|
| Good point. A language that gets updated by adding a lot of
| features is DIVERGING from a community that has mostly people
| that still use a lot of the C baggage in C++, and only a few
| folks that use a lot of template abstraction at the other end
| of the spectrum.
|
| Since in larger systems, you will want to re-use a lot of
| code via open source libraries, one is inevitably stuck in
| not just one past, but several versions of older C++,
| depending on when the code to be re-used was written, what
| C++ standard was stable enough then, and whether or not the
| author adopted what part of it.
|
| Not to speak of paradigm choice to be made (object oriented
| versus functional versus generic programmic w/ templates).
|
| It's easier to have, like Rust offers it, a single way of
| doing things properly. (But what I miss in Rust is a single
| streamlined standard library - organized class library - like
| Java has had it from early days on, it instead feels like "a
| pile of crates").
| pjmlp wrote:
| Just give Rust 36 years of field use, to see how it goes.
| timschmidt wrote:
| 36 years is counting from the first CFront release.
| Counting the same way for Rust, it's been around since
| 2006. It's got almost 20 years under it's belt already.
|
| edit: what's with people downvoting a straight fact?
| pjmlp wrote:
| Because it is counting since CFront 2.0, the first
| official release with industry use in UNIX systems.
|
| So that would be Rust 1.0, released in 2015, not 2006,
| putting it down to a decade.
|
| And the point still stands when looking at any long
| enough ecosystem still in use, with strong backwards
| compatibility, not only the language, the whole
| ecosystem, eventually editions alone won't make it, and
| just like those languages, Rust will gain its own warts.
| timschmidt wrote:
| Fair enough. I can cop to getting the CFront date wrong.
| Still, a decade since 1.0 is non-trivial.
|
| > eventually editions alone won't make it, and just like
| those languages, Rust will gain its own warts.
|
| That's possible. Though C++ hasn't had editions, or the
| HLIR / MIR separation, the increased strictness,
| wonderful tooling, or the benefit of learning from the
| mistakes made with C++. Noting that, it seems reasonable
| to expect Rust to collect less cruft and paint itself
| into fewer corners over a similar period of time. Since
| C++ has been going for 36 years, it seems Rust will
| outlive me. Past that, I'm not sure I care.
| pjmlp wrote:
| C++ editions are -std=something, people keep forgeting
| Rust editions are quite limited in what they actually
| allow in grammar and semantic changes across versions,
| and they don't cover standard library changes.
|
| IDEs are wonderful tooling, maybe people should get their
| heads outside UNIX CLIs and MS-DOS like TUIs.
|
| Then there is the whole ecosystem of libraries, books,
| SDKs and industry standards.
| timschmidt wrote:
| I'm not sure who in your mind is forgetting that, or what
| the rest of your comment means to communicate.
|
| Who are you speaking to who hasn't explored all those
| things in depth?
|
| I see Rust's restrictions as a huge advantage over C++
| here. Even with respect to editions. Rust has always
| given me the impression of a language designed from the
| start to be approximately what C++ is today, without the
| cruft, in which safety is opt-out, not opt-in. And the
| restrictions seem more likely to preserve that than not.
|
| C/C++ folks seem to see Rust's restrictions as anti-
| features without realizing that C/C++'s lack of
| restriction resulted in the situation they have today.
|
| I only maintain a few projects in each language, so I
| haven't run into every sort of issue for either, but
| that's very much how it feels to me still, several years
| and several projects in.
| pjmlp wrote:
| Many of the members of the Rust Evangelism Strike Force,
| as main audience. That is to whom it is targeted for,
| given the usual kind of content that some write about.
|
| I agree that Rust is designed to be like C++ is today,
| without the cruft, except all languages if they survive
| long enough in the market, beyond the adoption curve,
| they will eventually get their own cruft.
|
| Not realizing this, will only make that 30 years from
| now, if current languages haven't yet been fully replaced
| by AI based tools, there will be that language designed
| to be like Rust is in 30 years, but without the cruft.
|
| The strength of C++ code today is on the ecosystem, that
| is why we reach for it, having to write CUDA, DirectX,
| maybe dive into the innards of Java, CLR, V8, GCC, LLVM,
| doing HPC with OpenAAC, OpenMP, MPI, Metal, Unreal,
| Godot, Unity.
|
| Likewise I don't reach for C for fun, the less the
| merrier, rather POSIX, OpenGL, Vulkan,....
| timschmidt wrote:
| > Many of the members of the Rust Evangelism Strike
| Force, as main audience.
|
| Well I'm not them. I'm just a regular old software
| developer.
|
| > The strength of C++ code today is on the ecosystem
|
| Ecosystem is why I jumped ship from C++ to Rust. The
| difference in difficulty integrating a random library
| into my project is night and day. What might take a week
| or a month in C++ (integrating disparate build systems,
| establishing types and lifetimes of library objects and
| function calls, etc) takes me 20 minutes in Rust. And in
| general I find the libraries to be much smaller, more
| modular, and easier to consume piecemeal rather than a
| whole BOOST or QT at a time.
|
| And while the Rust libraries are younger, I find them to
| be more stable, and often more featureful and with better
| code coverage. The language seems to lend itself to
| completionism.
| d_tr wrote:
| Rust 0.1, the first public release, came out in January
| 2012. CFront 1.0, the first commercial release, came out
| in 1985.
|
| The public existence of Rust is 13 years, during which
| computing has not changed that much to be honest. Now
| compare this to the prehistory that is 1985, when CFront
| came out, already made for backwards compatibility with
| C.
| timschmidt wrote:
| I grew up with all the classic 8 bit micros, and to be
| honest, it doesn't feel like computing has changed at all
| since 1985. My workstation, while a billion times faster,
| is still code compatible with a Datapoint 2200 from 1970.
|
| The memory model, interrupt model, packetized networking,
| digital storage, all function more or less identically.
|
| In embedded, I still see Z80s and M68ks like nothing's
| changed.
|
| I'd love to see more concrete implementations of
| adiabatic circuits, weird architectures like the mill,
| integrated FPGAs, etc. HP's The Machine effort was a rare
| exciting new thing until they walked back all the
| exciting parts. CXL seems like about the most interesting
| new thing in a bit.
| qznc wrote:
| Today a byte is 8 bits. That was not always the case back
| then, for example.
| timschmidt wrote:
| > I grew up with all the classic 8 bit micros
|
| Meaning that all the machines I've ever cared about have
| had 8 bit bytes. The TI-99/4A, TRS-80, Commodore 64 and
| 128, Tandy 1000 8088, Apple ][, Macintosh Classic, etc.
|
| Many were launched in the late 70s. By 1985 we were well
| into the era of PC compatibles.
| bluGill wrote:
| in 1985 PC compatibles were talked about, but systems
| like VAX, and mainframes were still very common and
| considered the real computers while PCs were toys for
| executives. PCs had already shown enough value (via word
| processors and spreadsheets) that everyone knew they were
| not going away. PCs lacked things like multi-tasking that
| even then "real" computers had for decades.
| timschmidt wrote:
| > in 1985 PC compatibles were talked about
|
| My https://en.wikipedia.org/wiki/Tandy_1000 came out in
| 1984. And it was a relatively late entry to the market,
| it was near peak 8088 with what was considered high end
| graphics and sound for the day, far better than the IBM
| PC which debuted in 1981 and only lasted until 1987.
| mazurnification wrote:
| Does GPU thingy count as something that has changed with
| computing?
| timschmidt wrote:
| Yeah, I almost called that out. Probably should have.
| GPU/NPU feels new (at least for us folks who could never
| afford a Cray). Probably the biggest change in the last
| 20 years, especially if you classify it with other multi-
| core development.
| adolph wrote:
| It may go on to be as important as the FPU [0]. Amazingly
| enough you can still get one for a Classic II [1].
|
| 0. https://en.wikipedia.org/wiki/Floating-point_unit
|
| 1. https://www.tindie.com/products/jurassicomp/68882-fpu-
| card-f...
| mgaunard wrote:
| A lot of people using C++ don't actually use any libraries.
| I've observed the opposite with Rust.
|
| People choose C++ because it's a flexible language that
| lets you do whatever you want. Meanwhile Rust is a
| constrained and opinionated thing that only works if you do
| things "the right way".
| tialaramex wrote:
| > People choose C++ because it's a flexible language that
| lets you do whatever you want.
|
| You went on a bit too long. C++ lets you do whatever.
| Whether you _wanted_ that is not its concern. That 's
| handily illustrated in Matt Godbolt's talk - you provided
| a floating point value but that's inappropriate?
| Whatever. Negative values for unsigned? Whatever.
|
| This has terrible ergonomics and the consequences were
| entirely predictable.
| moomin wrote:
| I've seen it argued that, in practice, there's two C++
| communities. One is fundamentally OK with constantly
| upgrading their code (those with enterprise refactoring tools
| are obviously in this camp, but it's more a matter of
| attitude than technology) and those that aren't. C++ is
| fundamentally caught between those two.
| AndrewStephens wrote:
| This is the truth. I interview a lot of C++ programmers and
| it amazes me how many have gone their whole careers barely
| touching C++11 let alone anything later. The extreme reach
| of C++ software (embedded, UIs, apps, high-speed
| networking, services, gaming) is both a blessing and a
| curse and I understand why the committee is hesitant to
| introduce breaking changes at the expense of slow progress
| on things like reflection.
| 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.
| josephg wrote:
| Yes that's right. The feature really wants compiler
| support for that reason. The simplest version wouldn't be
| too hard to implement. Every function just exports a flag
| on whether or not it (or any callees) can panic. Then we
| have a nopanic keyword which emits a compiler error if
| the function (or any callee) panics.
|
| It would be annoying to use - as you say, you couldn't
| even add regular numbers together or index into an array
| in nopanic code. But there are ways to work around it
| (like the wrapping types).
|
| One problem is that implicit nopanic would add a new way
| to break semver compatibility in APIs. Eg, imagine a
| public api that just happens to not be able to panic. If
| the code is changed subtly, it could easily start
| panicing again. That could break callers, so it has to be
| a major version bump. You'd probably have to require
| explicit nopanic at api boundaries. (Else assume all
| public functions from other crates can panic). And
| because of that, public APIs like std would need to be
| plastered with nopanic markers everywhere. It's also not
| clear how that works through trait impls.
| j-krieger wrote:
| Yeah, this is how it works with no_std.
| kbolino wrote:
| No? https://godbolt.org/z/jEc36vP3P
|
| As far as I can tell, no_std doesn't change anything with
| regard to either the usability of panicking operators
| like integer division, slice indexing, etc. (they're
| still usable) nor on whether they panic on invalid input
| (they still do).
| 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.
| josephg wrote:
| You can handle panics. It's for unrecoverable errors, but
| internally it does stack unwinding by default like
| exceptions in C++.
|
| You see this whenever you use cargo test. If a single test
| panics, it doesn't abort the whole program. The panic is
| "caught". It still runs all the other tests and reports the
| failure.
| swiftcoder wrote:
| > but internally it does stack unwinding by default
|
| Although as a library vendor, you kind have to assume
| your library could be compiled into an app configured
| with panic=abort, in which case it will not do that
| 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.
| nextaccountic wrote:
| Obviously yes. For the same reason it's acceptable that
| myvec[i] panics (it will panic if i is out of bounds -
| but you already figured out that i is in bounds) and a /
| b panic for a and b integers (it will panic if b is zero,
| but if your code is not buggy you already tested if b is
| zero prior to dividing right?)
|
| Panic is absolutely fine for bugs, and it's indeed what
| should happen when code is buggy. That's because buggy
| code can make absolutely no guarantees on whether it is
| okay to continue (arbitrary data structures may be
| corrupted for instance)
|
| Indeed it's hard to "treat an error" when the error means
| code is buggy. Because you can rarely do anything
| meaningful about that.
|
| This is of course a problem for code that can't be
| interrupted.. which include the Linux kernel (they note
| the bug, but continue anyway) and embedded systems.
|
| Note that if panic=unwind you have the opportunity to
| catch the panic. This is usually done by systems that
| process multiple unrelated requests in the same program:
| in this case it's okay if only one such request will be
| aborted (in HTTP, it would return a 5xx error), provided
| you manually verify that no data structure shared by
| requests would possibly get corrupted. If you do one
| thread per request, Rust does this automatically; if you
| have a smaller threadpool with an async runtime, then the
| runtime need to catch panics for this to work.
| monkeyelite wrote:
| > Note that if panic=unwind you have the opportunity to
| catch the panic.
|
| And now your language has exceptions - which break
| control flow and make reasoning about a program very
| difficult - and hard to optimize for a compiler.
| wyager wrote:
| > only for absolutely unrecoverable errors
|
| Unfortunately even the Rust core language doesn't treat
| them this way.
|
| I think it's arguably the single biggest design mistake in
| the Rust language. It prevents a ton of useful stuff like
| temporarily moving out of mutable references.
|
| They've done a shockingly good job with the language
| overall, but this is definitely a wart.
| willtemperley wrote:
| Using a Rust lib from Swift on macOS I definitely want to
| catch panics - to access security scoped resources in Rust
| I need the Rust code to execute in process (I believe) but
| I'd also like it not to crash the entire app.
| monkeyelite wrote:
| > I probably really would prefer only that request to
| abort, not for the entire process to be torn down,
|
| This is a sign you are writing an operating system instead
| of using one. Your web server should be handling requests
| from a pool of processes - so that you get _real_ memory
| isolation and can crash when there is a problem.
| tsimionescu wrote:
| Even if you used a pool of processes, that's still not
| one process per request, and you still don't want one
| request crashing to tear down unrelated requests.
| monkeyelite wrote:
| I question both things. I would first of all handle each
| request in its own process.
|
| If there was a special case that would not work, then the
| design dictates that requests are not independent and
| there must be risk of interference (they are in the same
| process!)
|
| What I definitely do not want is a bug ridden "crashable
| async sub task" system built in my web program.
| tsimionescu wrote:
| This is simply a wrong idea about how to write web
| servers. You're giving up scalability massively, only to
| gain a minor amount of safety - one that is virtually
| irrelevant in a memory safe language, which you should
| anyway use. The overhead of process-per-request, or even
| thread-per-request, is absurd if you're already using a
| memory safe language.
| monkeyelite wrote:
| > You're giving up scalability massively
|
| you're vastly over estimating the overhead of processes
| and number of simultaneous web connections.
|
| > only to gain a minor amount of safety
|
| What you're telling me is performance (memory?) is such a
| high priority you're willing to make correctness and
| security tradeoffs.
|
| And I'm saying thats ok, one of those is crashing might
| bring down more than one request.
|
| > one that is virtually irrelevant in a memory safe
| language
|
| Your memory safe language uses C libraries in its
| process.
|
| Memory safe languages have bugs _all the time_. The
| attack surface is every line of your program and runtime.
|
| Memory is only one kind of resource and privilege.
| Process isolation is key for managing resource access -
| for example file descriptors.
|
| Chrome is a case study if these principles. Everybody
| thought isolating JS and HTML pages should be easy -
| nobody could get it right and chrome instead wrapped each
| page in a process.
| kevincox wrote:
| > you're vastly over estimating the overhead of processes
| and number of simultaneous web connections.
|
| It's less the actual overhead of the process but the
| savings you get from sharing. You can reuse database
| connections, have in-memory caches, in-memory rate limits
| and various other things. You can use shared memory which
| is very difficult to manage or an additional common
| process, but either way you are effectively back to
| square one with regards to shared state that can be
| corrupted.
| monkeyelite wrote:
| You certainly can get savings. I question how often you
| need that.
|
| I just said one of the costs of those saving is crashing
| may bring down multiple requests - and you should design
| with that trade off.
| simiones wrote:
| Please find one web server being actively developed using
| one process per request.
|
| Handling thousands of concurrent requests is table stakes
| for a simple web server. Handling thousands of concurrent
| processes is beyond most OSs. The context switching
| overhead alone would consume much of the CPU of the
| system. Even hundreds of processes will mean a good
| fraction of the CPU being spent solely on context
| switching - which is a terrible place to be.
| nosefrog wrote:
| We did that at Dropbox in Python for a while. Though they
| switched to async after I left.
| monkeyelite wrote:
| > Handling thousands of concurrent processes is beyond
| most OS
|
| It works fine on Linux - the operating system for the
| internet. Have you tried it?
|
| > good fraction of the CPU being spent solely on context
| switching
|
| I was waiting for this one. Threads and processes do the
| same amount of context switching. The overhead of
| processes switch is a little higher. The main cost is
| memory.
| ordu wrote:
| > Threads and processes do the same amount of context
| switching.
|
| Yes, therefore real webservers use a limited amount of
| threads/processes (in the same ballpark as a number of
| CPU cores). Modern approach is to use green threads which
| are really cheap to switch, it is like store registers,
| read registers and jmp.
|
| > The main cost is memory.
|
| The main cost is scheduling, not switching per se.
| Preemptive multitasking needs to deal with priorities to
| not waste time, and algorithms that do it are O(N)
| mostly. All these O(N) calculations needs to be completed
| multiple times per second, the higher the frequency of
| switching the more work to do. When you have thousands of
| processes it is the main cost. If you have tens of
| thousands it starts to bite hard.
| monkeyelite wrote:
| > The main cost is scheduling, not switching per se.
| Preemptive multitasking needs to deal with priorities to
| not waste time, and algorithms that do it
|
| The person I am having a conversation with is advocating
| for threads instead of processes. How do you think
| threads work?
|
| > Modern approach is to use green threads which are
| really cheap to switch, it is like store registers, read
| registers and jmp.
|
| That's certainly the popular approach. As I said at the
| beginning this approach is making a mini operating system
| with more bugs and less security rather than leveraging
| the capabilities of your operating system.
|
| Once again, im waiting to here about your experience of
| maxing out processes and after that having to switch to
| green threads.
| j-krieger wrote:
| Honestly, I don't think libraries should ever panic. Just
| return an UnspecifiedError with some sort of string. I work
| daily with rust, but I wish no_std and an arbitrary
| no_panic would have better support.
| burntsushi wrote:
| Example docs for `foo() -> Result<(), UnspecifiedError>`:
| # Errors `foo` returns an error called
| `UnspecifiedError`, but this only happens when an
| anticipated bug in the implementation occurs. Since
| there are no known such bugs, this API never returns an
| error. If an error is ever returned, then that is
| proof that there is a bug in the implementation.
| This error should be rendered differently to end
| users to make it clear they've hit a bug and not just a
| normal error condition.
|
| Imagine if I designed `regex`'s API like this. What a
| shit show that would be.
|
| If you want a less flippant take down of this idea and a
| more complete description of my position, please see:
| https://burntsushi.net/unwrap/
|
| > Honestly, I don't think libraries should ever panic.
| Just return an UnspecifiedError with some sort of string.
|
| The latter is not a solution to the former. The latter is
| a solution to libraries having panicking _branches_. But
| panics or other logically incorrect behavior can still
| occur as a result of bugs.
| 0x457 wrote:
| Funny that as a user of this library, I would just unwrap
| this, and it results in the same outcome as if library
| panicked.
| 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).
| uecker wrote:
| In C I simply use -fsanitize=signed-integer-overflow if I
| expect no overflow and checked arithmetic when I need to
| handle overflow. I do not think this is worse than in any
| other languages and seems less annoying than Rust. If I
| am lazy, I let allocation failure trap on null pointer
| dereference which is also safe, out-of-bounds accesses
| are avoided by -fsanitize=bounds (I avoid pointer
| arithmetic and unsafe casts where I can and essentially
| treat it like Rust's "unsafe").
| tialaramex wrote:
| Rust provides a default integer of each common size and
| signedness, for which overflow is prohibited [but this
| prohibition may not be enforced in release compiled
| binaries depending on your chosen settings for the
| compiler, in this case what happens is not promised but
| today it will wrap - it's _wrong_ to write code which
| does this on purpose - see the wrapping types below if
| you want that - but it won 't cause UB if you do it
| anyway]
|
| Rust also provides Wrapping and Saturating wrapper types
| for these integers, which wrap (255 + 1 == 0) or saturate
| (255 + 1 == 255). Depending on your CPU either or both of
| these might just be "how the computer works anyway" and
| will accordingly be very fast. Neither of them is how
| humans normally think about arithmetic.
|
| Furthermore, Rust also provides operations which do all
| of the above, as well as the more fundamental "with
| carry" type operations where you get two results from the
| operation and must write your algorithms accordingly, and
| explicitly fallible operations where if you would
| overflow your operation reports that it did not succeed.
| 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.
| steveklabnik wrote:
| The problem is, everything should have been there since
| day 1. It's still unclear which API Rust should end up
| with, even today, which is why it isn't stable yet.
| JBits wrote:
| Looking forward to the API when it's stabilised. Have
| there been any updates on the progress of allocators of
| this general area of Rust over the past year?
| steveklabnik wrote:
| I haven't paid that close of attention, but there have
| been two major APIs that people seem to be deciding
| between. We'll see.
| johnisgood wrote:
| > Zig for example does it right by having explicit
| allocators from the start
|
| Odin has them, too, optionally (and usually).
| kllrnohj wrote:
| > Rust unfortunately picked the wrong default here for
| the sake of convenience, along with the default of
| assuming a global allocator. [...] Zig for example does
| it right by having explicit allocators from the start
|
| Rust picked the right default for applications that run
| in an OS whereas Zig picked the right default for
| embedded. Both are good for their respective domains,
| neither is good at both domains. Zig's choice is verbose
| and useless on a typical desktop OS, especially with
| overcommit, whereas Rust's choice is problematic for
| embedded where things just work differently.
| Arnavion wrote:
| Various kind of "desktop" applications like databases and
| video games use custom non-global allocators - per-
| thread, per arena, etc - because they have specific
| memory allocation and usage patterns that a generic
| allocator does not handle as well as targeted ones can.
|
| My current $dayjob involves a "server" application that
| needs to run in a strict memory limit. We had to write
| our own allocator and collections because the default
| ones' insistence on using GlobalAlloc infallibly doesn't
| work for us.
|
| Thinking that only "embedded" cares about custom
| allocators is just naive.
| michalsustr wrote:
| Did you publish these by any chance?
| Arnavion wrote:
| Sorry, the code is closed source.
| simonask wrote:
| I don't see why you would have to write your own - there
| are plenty of options in the crate ecosystem, but perhaps
| you found them insufficient?
|
| As a video game developer, I've found the case for custom
| general-purpose allocators pretty weak in practice. It's
| exceedingly rare that you really want complicated
| nonlinear data structures, such as hash maps, to use a
| bump-allocator. One rehash and your fixed size arena
| blows up completely.
|
| 95% of use cases are covered by reusing flat data
| structures (`Vec`, `BinaryHeap`, etc.) between frames.
| Arnavion wrote:
| The allocator we wrote for $dayjob is essentially a
| buffer pool with a configurable number of "tiers" of
| buffers. "Static tiers" have N pre-allocated buffers of S
| bytes each, where N and S are provided by configuration
| for each tier. The "dynamic" tier malloc's on demand and
| can provide up to S bytes; it tracks how many bytes it
| has currently allocated.
|
| Requests are matched against the smallest tier that can
| satisfy them (static tiers before dynamic). If no tier
| can satisfy it (static tiers are too small or empty,
| dynamic tier's "remaining" count is too low), then that's
| an allocation failure and handled by the caller
| accordingly. Eg if the request was for the initial buffer
| for accepting a client connection, the client is
| disconnected.
|
| When a buffer is returned to the allocator it's matched
| up to the tier it came from - if it came from a static
| tier it's placed back in that tier's list, if it came
| from the dynamic tier it's free()d and the tier's used
| counter is decremented.
|
| Buffers have a simple API similar to the bytes crate -
| "owned buffers" allow &mut access, "shared buffers"
| provide only & access and cloning them just increments a
| refcount, owned buffers can be split into smaller owned
| buffers or frozen into shared buffers, etc.
|
| The allocator also has an API to query its usage as an
| aggregate percentage, which can be used to do things like
| proactively perform backpressure on new connections
| (reject them and let them retry later or connect to a
| different server) when the pool is above a threshold
| while continuing to service existing connections without
| a threshold.
|
| The allocator can also be configured to allocate using
| `mmap(tempfile)` instead of malloc, because some parts of
| the server store small, infrequently-used data, so they
| can take the hit of storing their data "on disk", ie
| paged out of RAM, to leave RAM available for everything
| else. (We can't rely on the presence of a swapfile so
| there's no guarantee that regular memory will be able to
| be paged out.)
|
| As for crates.io, there is no option. We need local
| allocators because different parts of the server use
| different instances of the above allocator with different
| tier configs. Stable Rust only supports replacing
| GlobalAlloc; everything to do with local allocators is
| unstable, and we don't intend to switch to nightly just
| for this. Also FWIW our allocator has both a sync and
| async API for allocation (some of the allocator instances
| are expected to run at capacity most of the time, so
| async allocation with a timeout provides some slack and
| backpressure as opposed to rejecting requests
| synchronously and causing churn), so it won't completely
| line up with std::alloc::Allocator even if/when that does
| get stabilized. (But the async allocation is used in a
| localized part of the server so we might consider having
| both an Allocator impl and the async direct API.)
|
| And so because we need local allocators, we had to write
| our own replacements of Vec, Queue, Box, Arc, etc because
| the API for using custom A with them is also unstable.
| monkeyelite wrote:
| > there are plenty of options in the crate ecosystem
|
| Who writes the crates?
| kllrnohj wrote:
| > Thinking that only "embedded" cares about custom
| allocators is just naive.
|
| I said absolutely no such thing? In my $dayjob working on
| graphics I, too, have used custom allocators for various
| things, primarily in C++ though, not Rust. But that in no
| way makes the _default_ of a global allocator wrong, and
| often those custom allocators have specialized
| constraints that you can exploit with custom containers,
| too, so it 's not like you'd be reaching for the stdlib
| versions probably anyway.
| imtringued wrote:
| >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
|
| Going from panic to panic free in Rust is as simple as
| choosing 'function' vs 'try_function'. The actual
| mistakes in Rust were the ones where the non-try version
| should have produced a panic by default. Adding
| Box::try_new next to Box::new is easy.
|
| There are only two major applications of panic free code
| in Rust: critical sections inside mutexes and unsafe code
| (because panic safety is harder to write than panic free
| code). In almost every other case it is far more fruitful
| to use fuzzing and model checking to explicitly look for
| panics.
| estebank wrote:
| In order to have true ergonomic no_panic code in Rust
| you'd need to be able to have parametricity on the panic
| behavior: have a single Box::new that can be context
| determined to be panicky or Result based. It has to be
| context determined and not explicitly code determined so
| that the top most request for the no_panic version to be
| propagated all the way down to stdlib through the entire
| stack. If you squint just a bit, you can see this is the
| same as maybe async, and maybe const, and maybe allocate,
| and maybe wrapping/overflowing math, etc. So there's an
| option to just add try_ methods on the entire stdlib,
| which all the code between your API and the underlying
| API need to use/expose, or push for a generic language
| level mechanism for this. Which then complicates the
| language, compiler and library code further. Or do both.
| 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.
| tialaramex wrote:
| You can tell Linux that you don't want overcommit. You
| will probably discover that you're now even more
| miserable and change it back, but it's an option.
| wizzwizz4 wrote:
| Whenever I switch off overcommitting, _every_ program on
| my system (that I 'm using) dies, one by one, over the
| course of 2-5 seconds, followed by Xorg. It's quite
| pretty.
| Shorel wrote:
| I did that and even with enormous amounts of free memory,
| Chrome and other Chromium browsers just die.
|
| They require overcommit just to open an empty window.
| kllrnohj wrote:
| > Also many functions use addition and what is one supposed
| to do in case of overflow?
|
| Honestly this is where you'd throw an exception. It's a
| shame Rust refuses to have them, they are absolutely
| _perfect_ for things like this...
| brooke2k wrote:
| I'm confused by this, because a panic is essentially an
| exception. They can be thrown and caught (although it's
| extremely discouraged to do so).
|
| The only place where it would be different is if you
| explicitly set panics to abort instead of unwind, but
| that's not default behavior.
| kevin_thibedeau wrote:
| > what are they supposed to do when there is no memory
| left?
|
| You abandon the current activity and bubble up the error to
| a stage where that effort can be tossed out or retried
| sometime later. i.e. Use the same error handling approach
| you would have to use for any other unreliable operation
| like networking.
| johnisgood wrote:
| I do not want a library to panic though, I want to handle the
| error myself.
| bionhoward wrote:
| This is already a thing, I do this right now. You configure
| the linter to forbid panics, unwraps, and even arithmetic
| side effects at compile time.
|
| You can configure your lints in your workspace-level
| Cargo.toml (the folder of crates)
|
| """
|
| [workspace.lints.clippy]
|
| pedantic = { level = "warn", priority = -1 }
|
| # arithmetic_side_effects = "deny"
|
| unwrap_used = "deny"
|
| expect_used = "deny"
|
| panic = "deny"
|
| """
|
| then in your crate Cargo.toml """
|
| [lints]
|
| workspace = true
|
| """
|
| Then you can't even compile the code without proper error
| handling. Combine that with thiserror or anyhow with the
| backtrace feature and you can yeet errors with "?" operators
| or match on em, map_err, map_or_else, ignore them, etc
|
| [1] https://rust-lang.github.io/rust-
| clippy/master/index.html#un...
| aaronmdjones wrote:
| Indent by 4 spaces to get code blocks on HN.
| Like this
| craftkiller wrote:
| You only need 2 spaces:
| https://news.ycombinator.com/formatdoc
| steveklabnik wrote:
| You only need 2. https://news.ycombinator.com/formatdoc
|
| > Text after a blank line that is indented by two or more
| spaces is reproduced verbatim. (This is intended for
| code.)
| J_Shelby_J wrote:
| But can deny the use of all operations that might panic
| like indexing an array?
| weegee101 wrote:
| The issue with this in practice is that there are always
| cases where panics are absolutely the correct course of
| action. When program state is bad enough that you can't
| safely continue, you need to panic (and core dump in dev).
| Otherwise you are likely just creating an integrity
| minefield for you to debug later.
|
| Not saying there aren't applications where using these
| lints couldn't be alright (web servers maybe), but at least
| in my experiences (mostly doing CLI, graphics, and embedded
| stuff) trying to keep the program alive leads to more
| problems than less.
| 0x457 wrote:
| The comment you're replying to specifically wanted "no
| panics" version of rust.
|
| It's totally normal practice for a library to have this
| as a standard.
| 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...
| estebank wrote:
| For things like this I find that ? still works well enough,
| but I tend to write code like match x(y) {
| Ok(None) => "not found".into(), Ok(Some(x)) => x,
| Err(e) => handle_error(e), }
|
| Because of pattern matching, I often also have one arm for
| specific errors to handle them specifically in the same way
| as the ok branches above.
| 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.
| Seattle3503 wrote:
| Yes, this is often the best "low-hanging fruit" option,
| but it can get expensive. It depends how you value your
| developer time.
| hobofan wrote:
| Besides faster hardware, one of the main features (and
| drawbacks) you get with self-hosted runners is the option
| to break through build isolation, and have performant
| caches between builds.
|
| With many other build systems I'd be hesitant to do that,
| but since Cargo is very good about what to rebuild for
| incremental builds, keeping the cache around is a huge
| speed boost.
| sethammons wrote:
| We have 60 minutes deploy pipelines and are in python. Just
| mentioning that since, in theory, we are not penalized for
| long compile times.
|
| Fast ability to quickly test and get feedback is mana from
| the gods in software development. Organizations should keep
| it right below customer satisfaction and growth as a
| driving metric.
| 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?
| alpaca128 wrote:
| Almost a quarter of Firefox' compiled code is Rust:
| https://4e6.github.io/firefox-lang-stats/
| 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.
| thawawaycold wrote:
| that does not answer at all OP's question.
| 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.
| jandrewrogers wrote:
| FWIW, stack traces are part of C++ now and you can construct
| custom error types that automagically attach them if desired.
| Result types largely already exist in recent C++ editions if
| you want them.
|
| I use completely custom error handling stacks in C++ and they
| are quite slick these days, thanks to improvements in the
| language.
| fpoling wrote:
| What I really like to see is stack traces annotated with
| values of selected local values. A few years ago I tried
| that in a C++ code base where exceptions were disabled
| using macros and something like error context passed by
| references. But the result was ugly and I realized that I
| had zero chances to adopt it.
|
| With Rust Result and powerful macros it easier to
| implement.
| 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/...
| zaphar wrote:
| Counterpoint: Decades of C++/Python/Java/... has strongly
| biased me against the try/except pattern.
|
| It's obviously subjective in many ways. However, what I
| dislike the most is that try/except hides the error path from
| me when I'm reading code. Decades of trying to figure out why
| that stacktrace is happening in production suddenly has given
| me a strong dislike for that path being hidden from me when
| I'm writing my code.
| sham1 wrote:
| There should be a way to have the function/method document
| what sort of stuff can go wrong, and what kinds of
| exceptions you can get out of it.
|
| It could be some kind of an exception check thing, where
| you would either have to make sure that you handle the
| error locally somehow, or propagate it upwards. Sadly
| programming is not ready for such ideas yet.
|
| ---
|
| I jest, but this is exactly what checked exceptions are
| for. And the irony of stuff like Rust's use of `Result<T,
| E>` and similarly ML-ey stuff is that in practice they end
| up with what are essentially just checked exceptions,
| except with the error type information being somewhere
| else.
|
| Of course, people might argue that checked exceptions suck
| because they've seen the way Java has handled them, but
| like... that's Java. And I'm sorry, but Java isn't the
| definition of how checked exceptions can work. But because
| of Java having "tainted" the idea, it's not explored any
| further, because we instead just assume that it's bad by
| construction and then end up doing the same thing anyway,
| only slightly different.
| Analemma_ wrote:
| > There should be a way to have the function/method
| document what sort of stuff can go wrong, and what kinds
| of exceptions you can get out of it.
|
| The key phrase you're looking for is "algebraic effect
| systems". Right now they're a pretty esoteric thing only
| really seen in PL research, but at one point so was most
| of the stuff we now take for granted in Rust. Maybe
| someday they'll make their way to mainstream languages in
| an ergonomic way.
| SJMG wrote:
| I totally agree with you that Java checked exceptions
| suck. IME exceptions are far more ergonomic than Result.
|
| Nim has a good take on exception tracking that's elegant,
| performant, and only on request (unlike Java's attempt).
| 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.
| flohofwoe wrote:
| IMHO the ugly thing about Result and Option (and a couple of
| other Rust features) is that they are stdlib types, basic
| functionality like this should be language syntax (this is also
| my main critique of 'modern C++').
|
| And those 'special' stdlib types wouldn't be half as useful
| without supporting language syntax, so why not go the full way
| and just implement everything in the language?
| choeger wrote:
| Uh, nope. Your language needs to be able to define these
| types. So they belong into the stdlib because they are
| useful, not because they are special.
|
| You might add syntactic sugar on top, but you don't want
| these kinds of things in your fundamental language
| definition.
| stodor89 wrote:
| Failure is not an option, it's a Result<T,E>
| chickenzzzzu wrote:
| why not just read the function you are calling to determine the
| way it expects you to handle errors?
|
| after all, if a library exposes too many functions to you, it
| isn't a good library.
|
| what good is it for me to have a result type if i have to call
| 27 functions with 27 different result types just to rotate a
| cube?
| fooker wrote:
| One of the strengths of C++ is the ability to build features
| like this as a library, and not hardcode it into the language
| design.
|
| Unless you specifically want the '?' operator, you can get
| pretty close to this with some clever use of templates and
| operator overloading.
|
| If universal function call syntax becomes standardized, this
| will look even more functional and elegant.
| steveklabnik wrote:
| Rust also started with it as a library, as try!, before ?.
| There were reasons why it was worth making syntax, after
| years of experience with it as a macro.
| scotty79 wrote:
| > Then you top it on with `?` shortcut
|
| I really wish java used `?` as a shorthand to declare and
| propagate checked exceptions of called function.
| tomp wrote:
| Did you ever actually program in Rust?
|
| In my experience, a lot of the code is dedicated to "correctly
| transforming between different Result / Error types".
|
| Much more verbose than exceptions, despite _most_ of the time
| _pretending_ they 're just exceptions (i.e. the `?` operator).
|
| Why not just implement exceptions instead?
|
| (TBH I fully expect this comment to be downvoted, then Rust to
| implement exceptions in 10 years... Something similar happened
| when I suggested generics in Go.)
| nomel wrote:
| I've only worked in exceptions, so I can't really comprehend
| the book-keeping required without them. To me it's a
| separation of concerns: the happy path only involves happy
| code. The "side channel" for the unhappy path is an
| exception, with an exception handler at a layer of the
| abstraction where it's meaningful, yet happy, code. By
| "happy" I mean code that's simply the direct practical work
| that's trying to accomplished something, so doesn't _need_ to
| worry about when things go terribly wrong.
|
| Being blind to the alternative, and mostly authoring lower
| level libraries, what's the _benefit_ of not having
| exceptions? I understand how they 're completely
| inappropriate for an OS, a realtime system, etc, but what
| about the rest? Or is that the problem: once you have the
| concept, you've polluted everything?
| alpaca128 wrote:
| In places where you only want to pass an error to the
| caller, Rust lets you just add "?" to the call that returns
| a Result/Option. And the Rust compiler will check that
| every potential error is handled.
|
| I wouldn't say that it's the tedious part of the language.
| tomp wrote:
| There's mostly _drawback_ of _having_ exceptions, I 'm not
| really aware of any _benefits_ of not having them.
|
| People often complain about:
|
| - performance impact, either speed (most languages) or
| binary size (C++); this, however, is mostly an
| _implementation_ concern, and doesn 't impact Rust _at all_
| , as exceptions can simply be syntax-level compiler sugar,
| and can be compiled _exactly_ the same as Result type is
| currently (having said that, the optional _stack trace_ is
| another potential issue, which _would_ have a performance
| impact even in Rust)
|
| - checked exceptions - this is particularly a concern in
| Java, which has a fairly poor type system (closed subtyping
| only) and no type inference, so declaring all unhandled
| exceptions is tedious
|
| - non-checked exceptions - in this case, _" every exception
| can happen anywhere"_ so it's ostensibly unsafe (well it's
| just how life is, and even Java has special unchecked
| exceptions such as OutOfMemory that _can_ happen anywhere)
| - some people claim that "exceptions can't just randomly
| jump out of code" is a _benefit_ of not having exceptions
| but usually those people sweep OutOfMemory and
| DivisionByZero under the rug (e.g. Rust, where they just
| "crash" the program)
|
| Rust would obviously fit the checked exceptions path, as
| the Result implementatoin basically is "poor-man's checked
| exceptions". It only needs to flip the syntax sugar -
| propagate by default (and implicitly), "catch" and
| materialize using `?` - as well as making Error an _open_
| enum (such that you can add _cases_ implicitly - see e.g.
| OCaml 's exception type `exn` [1]) - and that's basically
| it!
|
| [1] https://ocaml.org/manual/5.3/extensiblevariants.html
| hoppp wrote:
| Its true but using unwrap is a bit boring , I mean...boring is
| good but its also boring.
| craftkiller wrote:
| You shouldn't be using unwrap.
| 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.
| bigstrat2003 wrote:
| The Rust community is helpful... but also quite political
| and _extremely_ hostile to anyone who doesn 't share
| those politics. Even something as anodyne as saying
| "let's keep politics out of technical discussion" is
| frequently met with hostility (because many community
| members believe that tech is inherently political and
| that trying to keep politics out is really just a bad
| faith attempt to frame things in terms of the requester's
| preferred politics). It's also full of drama in a way
| that other communities online simply aren't. For example,
| the drama that happened when the guy behind thephd.dev
| got invited to give the keynote at rustconf, then his
| talk was downgraded from the keynote - everyone involved
| in that mess (including other bloggers who weighed in)
| came off as immature and not someone you would ever want
| to work with.
|
| I like the Rust language quite a bit. I find the Rust
| _community_ to be one of the most toxic places in the
| entire tech business. Your mileage may vary and that 's
| fine of course - but plenty of people want to stay far
| away from a community that acts like the Rust community
| does.
| kragen wrote:
| JeanHeyd's side of the RustConf thing:
| https://thephd.dev/i-am-no-longer-speaking-at-
| rustconf-2023
| Ygg2 wrote:
| To be clear, everyone in Rust community (on Reddit,
| Twitter, etc) was shocked by this, and people started
| asking for explanations. This led to several people
| stepping down, and seems to have been miscommunication
| between Rust Foundation and Rust Project.
| kragen wrote:
| I'd like to read the other sides of the story; do you
| have any recommendations?
|
| On the surface it sounds like a community with such deep
| pathology that it will take at least a generation
| following a complete change of leadership to have a
| chance at recovery. But there are three sides to every
| story.
| Ygg2 wrote:
| I think the best summary is this:
| https://fasterthanli.me/articles/the-rustconf-keynote-
| fiasco...
|
| > On the surface it sounds like a community with such
| deep pathology
|
| First what sort of pathology? You're confusing community
| with leadership.
|
| The community didn't want this, and leadership was doing
| a restructuring due to change from Foundation and
| Project. Welcome to OSS projects.
|
| Second as opposed to what?
|
| A community at the beck and call of your CEO dictator?
| I'm a Java dev, so all it takes for Java to die is for
| One Rich Asshole Called Larry Ellison to decide that they
| (ORACLE) are inserting two mandatory ads to be watched
| during each Java compiler run. Or god forbid that they
| will monetize Java.
|
| Plus if I had 24/7 insight into how Oracle worked, I'd
| probably also be much less inclined to join Java as a new
| dev.
|
| To paraphrase Tolstoy: (All perfect languages are dead;)
| Each imperfect language is imperfect in its own way.
| BlueTemplar wrote:
| For where I am concerned, I don't want to have anything
| to do with the kind of developers that still think that
| it's acceptable to use Github, VS Code, or Discord in
| 2025 in a professional setting, much worse teach a new
| generation of developers to use them : that's like being
| a doctor and giving out cigarettes to children.
| jpc0 wrote:
| Rust libraries tend to over abstract and then need large
| refactors when those abstractions fall apart. When I've
| complained about it in the past I've been met with "You
| would need the abstraction eventually". Maybe, but I'm
| also capable of building it myself it it gets to that.
|
| Maybe that's more of a bias with rust media stuff, seems
| to be going deeper into that rabbit hole though.
|
| The community was at least, may still be, very sensitive
| to rust being criticised. I genuinely brought an example
| of a provably correct piece of code that the borrow
| checker wouldn't accept, interior mutability problem. I
| was I should build a massive abstraction to avoid the
| problem and that I'm holding it wrong... Put me off the
| language for a few years, it shouldn't have, I should
| have just ignored the people and continued on but we all
| get older and learn things.
| simonask wrote:
| I think there's a bit of mismanaged expectations,
| combined with a community that, while generally helpful,
| suffers from a bit of fatigue from constantly dispelling
| myths and falsehoods about the language, often presented
| in bad faith.
|
| My favorite is when Rust gets dragged into weird American
| "culture wars" - somehow, it's a "woke" language? (And
| somehow, that's a problem?)
|
| But yeah, the language docs are pretty up front about the
| fact that the borrow checker sometimes rejects code that
| is provably fine, so it's a weird criticism. The
| nontrivial breakthrough was that Rust proved that a huge
| amount of nontrivial code can be written within the
| restrictions of the borrow checker, eliminating swaths of
| risk factors without a resource penalty.
| 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.
| kevincox wrote:
| Yeah, this is one of the few things that I love about Swift.
| I think it gets it exactly right that keyword arguments
| should be the default and you can opt out in cases where the
| keyword is really unnecessary.
| 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.
| const_cast wrote:
| This only really applies to languages that don't check this
| at compile-time. I don't consider compile-time errors a
| foot gun. I mean, it should be impossible for that kind of
| bad code to ever get merged in most reasonable CI/CD
| processes.
| Ygg2 wrote:
| No? This happens in any language that has keyword args.
|
| If I delete/rename a field of a class in any statically
| checked language, it's going to report a compile error,
| and it's still a breaking change. Same thing with named
| arguments.
| const_cast wrote:
| Sure, but it doesn't actually matter because this can't
| ever manifest as a bug. Your PR will just be rejected,
| which to me, is correct behavior.
|
| Typically when you're changing a name you're changing
| behavior, too. This isn't just something we should let
| slip under the radar, otherwise bugs can actually
| manifest.
| Spivak wrote:
| I guess but you're changing your user-visible API so it
| should be a breaking change. In languages that don't have
| this type/arity is all that matters and the name is just
| nice sugar for the implementor who doesn't have to bind
| them to useful names.
|
| Even if you don't use keyword args your parameter names are
| still part of your API surface in Python because callers
| can directly name positional args. Only recently have you
| been able enforce unnamed positional only args as well as
| the opposite.
| dcdgo wrote:
| Rust Tuples can be destructured into named variables, or Enums
| can be used as Monads which give a label to a tuple of
| variables. Rust Enums are real fun to use so I encourage you to
| dive in. https://doc.rust-lang.org/rust-by-
| example/custom_types/enum....
| Gazoche wrote:
| Agreed, coming from Python it's one of the main things I miss
| in Rust. You can achieve something similar with the builder
| pattern or with structs + the Default trait, but it takes much
| more effort.
| 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.
| renox wrote:
| That's funny, if memory serves 'as' should be avoided in
| Rust and other casts should be used. That's a Rust wart
| which cannot be fixed..
| Arnavion wrote:
| Yes that's correct, for exactly the reason that it is
| more likely to keep compiling and possibly not do what
| you intended if the original value's type changes due to
| refactoring. However there are still a few conversions
| that don't have alternatives to `as` - truncating
| conversions (eg i64 -> i32 that intentionally discards
| the upper half), int <-> float conversions (eg i64 ->
| f64, both truncating and checked conversions), unsized
| pointer casts (eg *const [MaybeUninit<u8>] -> *const
| [u8], `.cast()` only works for Sized target), and
| probably a few more.
| 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.
| pjmlp wrote:
| I beg to differ, the same reasoning applies to Rust,
| otherwise there would not be a clippy at all.
| uecker wrote:
| The reason certain warnings are on or off by default in
| compilers in certain warnings modes depends on whether
| enough people find them useful enough or not. Rust caters
| to people who want strictness which makes it annoying to
| use for others, but if you want this you can also - to a
| large degree - have this in C and C++.
| 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).
| bigstrat2003 wrote:
| And? I don't really see the issue. It works, it is sound,
| and it has a nice clean interface for safe code to use.
| That's all I really ask for. Lots of useful things in
| programming are quite gnarly under the hood, but that
| doesn't mean those things aren't worth using.
| uecker wrote:
| It is fine, there is just not much Rust safety advantage
| left then. Also in C/C++ the errors do not usually occur
| when using a nicely defined API, but when doing the low-
| level gnarly stuff and getting it wrong. As said before,
| I think there is some advantage of Rust having a safe and
| unsafe subset, but is is nowhere as big as people claim
| it is.
| bigstrat2003 wrote:
| > It is fine, there is just not much Rust safety
| advantage left then.
|
| There's exactly as much as there was before though. The
| _entire point_ of the Rust safety paradigm is that you
| can guarantee that unsafe code is confined to only where
| it is needed. Nobody ever promised "you will never have
| to write unsafe code", because that would be clearly
| unfeasible for the systems programming domain Rust is
| trying to work in.
|
| I frankly cannot understand why people are so willing to
| throw the baby out with the bathwater when it comes to
| Rust safety. It makes no sense to me to say "my code
| needs to have some % unsafe, so I'll just make it 100%
| unsafe then" (which is effectively what one does when
| they use C or C++ instead). Why insist on not taking any
| safety gains at all when one can't have 100% gain?
| sunshowers wrote:
| Is there a safety advantage to using Java given that the
| HotSpot JVM is written in C++?
|
| All safe code is built on a foundation of unsafe code.
| 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)
| jpc0 wrote:
| My point is a Width type is usually not the sound type
| you are looking for. What probably wanted was a size type
| which is width and height. Or a dimensions type which is
| width and height. The problem was maybe not two arguments
| being confused but in reality a single thing with two
| elements...
| rq1 wrote:
| Ah I see, it's a solution too!
| 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.
| kanbankaren wrote:
| > with very strict guidelines.
|
| Low level languages always came with a set of usage
| guidelines. Either you make the language safe for anyone
| that they can't shoot themselves in the foot and end up
| sacrificing performance, or provide guidelines on how to
| use it while retaining the ability to extract maximum
| performance from the hardware.
|
| C/C++ shouldn't be approached like programming in
| Javascript/Python/Perl.
| zaphar wrote:
| And yet, Rust doesn't sacrifice performance and it has
| all kinds of those guardrails.
| uecker wrote:
| I think it is a bit myth that Rust does not sacrifice
| performance. If you stick to the safe part Rust usually
| does not seem to achieve the performance of C/C++
| according to what I have seen and read. I agree that the
| cleaner separation of unsafe and safe parts is an
| advantage of Rust.
| 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.
| quietbritishjim wrote:
| I suppose the sense it is backwards incompatible is that
| library authors have named their parameters without intended
| to make them part of the public interface that they commit to
| maintaining. Perhaps it could be made backwards compatible by
| being opt in function declarations but that would seem like a
| bit of a pain.
| 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.
| monkeyelite wrote:
| Making something longer doesn't make it easier to read,
| especially in repetition.
| 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.
| skywhopper wrote:
| So those editors could just insert the names for you. The
| bytes in the source file are not a serious concern, are they?
| 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.
| Maxatar wrote:
| The Sapir Whorf hypothesis has long been debunked:
|
| https://en.m.wikipedia.org/wiki/Linguistic_relativity
| oasisaimlessly wrote:
| The strong hypothesis has been debunked, yes, but nobody is
| asserting it.
|
| From your link:
|
| > Nevertheless, research has produced positive empirical
| evidence supporting a weaker version of linguistic
| relativity:[5][4] that a language's structures influence a
| speaker's perceptions, without strictly limiting or
| obstructing them.
| 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.
| codedokode wrote:
| The choice doesn't make sense because you want the
| program to always behave correctly and not only during
| development.
| lytedev wrote:
| Eh, maybe. There's a performance tradeoff here and
| maintainers opted for performance. I'm sure many folks
| would agree with you that it was the wrong choice, and
| I'm sure many folks would disagree with you that it was
| the wrong choice.
|
| There are also specific methods for doing *erflow-checked
| arithmetic if you like.
| codedokode wrote:
| Why should there be a performance tradeoff? Because Intel
| CPU doesn't have add-with-overflow-check instruction, we
| make our languages less safe?
| genrilz wrote:
| Actually, the x86 'ADD' instruction automagically sets
| the 'OF' and 'CF' flags for you for signed and unsigned
| overflow respectively [0]. So all you need to do to panic
| would be to follow that with an 'JO' or 'JB' instruction
| to the panic handling code. This is about as efficient as
| you could ask for, but a large sequence of arithmetic
| operations is still going to gum up the branch predictor,
| resulting in more pipeline stalls.
|
| [0]: https://www.felixcloutier.com/x86/add
| 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
| codedokode wrote:
| This is bad because the program behaves different
| depending on build flags. What's the point of having "use
| unsafe addition" flag, and having it enabled by default?
|
| Rust developers made a poor choice. They should have made
| a special function for unchecked addition and have "+"
| operator always panic on overflow.
| genrilz wrote:
| The point I'm sure was to prevent the checks from
| incurring runtime overhead in production. Even in release
| mode, the overflow will only wrap rather than trigger
| undefined behavior, so this won't cause memory corruption
| unless you are writing unsafe code that ignores the
| possibility of overflow.
|
| The checks being on in the debug config means your tests
| and replications of bug reports will catch overflow if
| they occur. If you are working on some sensitive
| application where you can't afford logic bugs from
| overflows but can afford panics/crashes, you can just
| turn on checks in release mode.
|
| If you are working on a library which is meant to do
| something sensible on overflow, you can use the wide
| variety of member functions such as 'wrapping_add' or
| 'checked_add' to control what happens on overflow
| regardless of build configuration.
|
| Finally, if your application can't afford to have logic
| bugs from overflows and also can't panic, you can use
| kani [0] to prove that overflow never happens.
|
| All in all, it seems to me like Rust supports a wide
| variety of use cases pretty nicely.
|
| [0]: https://github.com/model-checking/kani
| tialaramex wrote:
| Why do you want a panic? Shift left. Overflow can be
| rejected at compile time for a price that you might be able
| to afford - generality.
|
| Just insist that the programmer prove that overflow can't
| occur, and reject programs where the programmer couldn't or
| wouldn't do this.
| codedokode wrote:
| Programmer has other things to do. Computer or CPU should
| do the checks.
| 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.
| codedokode wrote:
| As a result, Rust developers themselves use wrapping addition
| where a checked addition should be used: https://doc.rust-
| lang.org/src/alloc/vec/mod.rs.html#2010
| tialaramex wrote:
| That's not a wrapping addition, that addition will never
| overflow so it has no overflow behaviour.
|
| This line could only overflow after we need to grow the
| container, so immediately this means the type T isn't a ZST
| as the Vec for ZSTs doesn't need storage and so it never
| grows.
|
| Because its not a ZST the maximum capacity in Rust is never
| bigger than isize::MAX which is an entire binary order of
| magnitude smaller than usize::MAX, as a result len + 1
| can't overflow the unsigned type, so this code is correct
| as written.
| thesuperbigfrog wrote:
| Ada has had overflow checks for decades:
|
| https://learn.adacore.com/courses/intro-to-ada/chapters/stro...
|
| And there is an Ada implementation that is part of GCC:
|
| https://www.gnu.org/software/gnat/
| ultimaweapon wrote:
| Rust is the only language I can easily control how integer
| overflow should behave. I can use `var1.wrapping_add(var2)` if
| I want the result to be wrapped or `var1.checked_add(var2)` if
| I don't want it to overflow.
| codedokode wrote:
| The functions are so verbose and inconvenient that even Rust
| developers themselves do not use them. For example, in this
| code [1] they used a wrapping addition instead of
| "checked_add" because it is faster to write.
|
| For comparison, Swift uses "+" for checked addition and as a
| result, majority of developers use checked addition by
| default. And in Rust due to its poor design choices most
| developers use wrapping addition even where a checked
| addition should be used.
|
| [1] https://doc.rust-lang.org/src/alloc/vec/mod.rs.html#2010
| ultimaweapon wrote:
| Checked addition by default will have too much overhead and
| it will hurt performance, which unacceptable in Rust since
| it was designed as a system language. Swift can use checked
| add by default since it was designed for application
| software.
|
| Your example code is not because it is faster to write, it
| is because it is impossible for its to overflow on that
| line.
| codedokode wrote:
| Why should checked addition have any overhead? You should
| just use checked addition instruction (on architectures
| that support it) instead of wrapping addition.
|
| Or just because on Intel CPUs it has overhead, we must
| forget about writing safer code?
| genrilz wrote:
| The reason '+ 1' is fine in the example you gave is that
| length is always less than or equal to capacity. If you
| follow 'grow_one' which was earlier in the function to grow
| the capacity by one if needed, you will find that it leads
| to the checked addition in [0], which returns an error that
| [1] catches and turns into a panic. So using '+1' prevents
| a redundant check in release mode while still adding the
| check in debug mode in case future code changes break the
| 'len <= capacity' invariant.
|
| Of course, if you don't trust the standard library, you can
| turn on overflow checks in release mode too. However, the
| standard library is well tested and I think most people
| would appreciate the speed from eliding redundant checks.
| [0]: https://doc.rust-
| lang.org/src/alloc/raw_vec.rs.html#651 [1]:
| https://doc.rust-lang.org/src/alloc/raw_vec.rs.html#567
| 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.
| monkeyelite wrote:
| What are you responding to from the article?
| Winsaucerer wrote:
| The title.
| 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?
| bluGill wrote:
| Who knows. It will likely have more than any other
| language. Though it will also continue to not get credit
| for things it got right.
|
| There will always remain two types of languages: those
| that nobody uses and those that everybody complains
| about.
| simonask wrote:
| Rust has rough edges today. The field of programming is
| still only a few decades old, and there's no doubt that
| something even shinier will come along, we just don't know
| yet what that looks like.
|
| That's not a good reason to stick with inferior tools now,
| though.
| bluGill wrote:
| What does inferior mean?
|
| Rust is inferior to C++ for my needs. This is just a
| reflection on we started a large project in C++ before
| rust existed, and now have millions of lines. Getting
| Rust to work with our existing C++ is hard enough as to
| not be worth it. Rewriting in Rust would cost 1 billion
| dollars. Thus despite all the problems we have with C++
| that Rust would solve, rust is inferior.
|
| (Rust is working on their C++ interoperability story and
| we are making changes that will allow using Rust in the
| future so I reserve the right to change this story in a
| few years, but only time will tell)
| taylorallred wrote:
| Cool that you're using areas/pools for lifetimes. Are you also
| using custom data structures or stl (out of curiosity)?
| thrwyexecbrain wrote:
| Nothing fancy, I found that one can do almost anything with
| std::vector, a good enough hash map and a simple hand-rolled
| intrusive list.
| senderista wrote:
| std::vector is the only STL container that makes sense to
| use in practice.
| 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.
| pjmlp wrote:
| Unfortunely thanks to the "code C in C++ crowd", there is
| this urban myth that goodies like proper encapsulation,
| stronger types, RAII, were not even available in pre-C++98,
| aka C++ARM.
|
| Meanwhile it was one of the reasons after Turbo Pascal, my
| next favourite programming language became C++.
|
| For me mastering C, after 1992, only became mattered because
| as professional, that is something that occasionally I have
| to delve into so better know your tools even if the grip
| itself has sharp corners, otherwise everytime the option was
| constrained to either C or C++, I always pick C++.
| simonask wrote:
| The strong/weak distinction is a bit fuzzy, but reasonable
| people can have the opinion that C++ is, in fact,
| loosely/weakly typed. There are countless ways to bypass the
| type system, and there are implicit conversions everywhere.
|
| It _is_ statically typed, though, so it falls in a weird
| category of loosely _and_ statically typed languages.
| mountainriver wrote:
| I still find it basically impossible to get started with a C++
| project.
|
| I tried again recently for a proxy I was writing thinking
| surely things have evolved at this point. Every single package
| manager couldn't handle my very basic and very popular
| dependencies. I mean I tried every single one. This is
| completely insane to me.
|
| Not to mention just figuring out how to build it after that
| which was a massive headache and an ongoing one.
|
| Compared to Rust it's just night and day.
|
| Outside of embedded programming or some special use cases I
| have literally no idea why anyone would ever write C++. I'm
| convinced it's a bunch of masochists
| almostgotcaught wrote:
| > Every single package manager couldn't handle my very basic
| and very popular dependencies
|
| Well there's your problem - no serious project uses one.
|
| > I'm convinced it's a bunch of masochists
|
| People use cpp because it's a mature language with mature
| tooling and an enormous number of mature libraries. Same
| exact reason anyone uses any language for serious work.
| cratermoon wrote:
| How can you simultaneously call cpp a mature language with
| mature tooling _and_ acknowledge that there 's no working
| package manager used by any "serious" project?
| almostgotcaught wrote:
| Do you people really not realize how completely asinine
| you sound with these lowbrow comments? I'll give you a
| hint: did you know that C also has no package manager?
| adgjlsfhk1 wrote:
| Yeah, and it's also much worse for it. There's a reason
| everyone in C uses their own linked list implementation
| and it's not because it's a platonic ideal of perfect
| software.
| almostgotcaught wrote:
| The question wasn't whether C/C++ are platonic ideals,
| the question was whether a language can be mature without
| a package manager.
| josephg wrote:
| If we take "mature" to mean "old" then yes - C and C++
| are certainly old. If we take "mature" to mean "good",
| then my answer changes.
| imtringued wrote:
| Agreed. Getting started with a C or C++ project is such a
| pain in the ass that I won't even bother. Then there is
| the fact that unless you have special requirements that
| necessitate C/C++, those languages have nothing going for
| them.
| const_cast wrote:
| Package managers per language are a (relatively) new
| endeavor. The oldest language I can think of that widely
| adopted it was Perl. Although, perl was quite ahead of
| it's time in a lot of ways, and php undid some the work
| of perl and went back to popularizing _include_ type
| dependencies instead of formal modules with a package
| manager.
|
| C++ "gets away" with it because of templates. Many
| (most?) libraries are mostly templates, or at the very
| least contain templates. So you're forced into include-
| style dependencies and it's pretty painless. For a good
| library, it's often downloading a single file and just
| _#include_ -ing it.
|
| C++ is getting modules now, and maybe that will spur a
| new interest in package managers. Or maybe not, it might
| be too late.
| simonask wrote:
| It's a relatively new endeavor, but it's also a
| requirement in 2025 if you want to be portable. The Linux
| ecosystem was focusing on installing dependencies system-
| wide for decades (that's how traditional `./configure.sh`
| expects things to work), and this approach is just
| inferior in so many ways.
|
| The shenanigans people get into with CMake, Conan, vcpkg,
| and so on is a patchwork of nightmares and a huge time
| sink compared to superior solutions that people have
| gotten used to in other languages, including Rust.
| const_cast wrote:
| I don't think it's a requirement and it does actually
| come with it's own set of foot guns. The problem is that
| these design decisions impact culture, which is why JS
| has a culture of importing far too many third-party
| packages. Which comes with it's own set of risks.
|
| I agree overall that the C or C++ way of doing things is
| more cumbersome, but I don't think that's enough to write
| off those languages as a whole.
| tdiff wrote:
| Because cpp is not meant for "rapid prototyping"
| involving importing half of github with single command.
| And the reality is that it works.
| simonask wrote:
| Works for whom?
|
| C++ build systems are notoriously brittle. When porting a
| project to a new platform, you're never just porting the
| code, you are also porting your build system. Every
| single project is bespoke in some way, sometimes because
| of taste, but most of the time because of necessity.
|
| It works because people spend a huge amount of time to
| make it work.
| tubs wrote:
| This seems hyperbolic. At work we cross compile the same
| code for a decent number of different platform - six
| different OS (Linux Mac windows and some embedded ones)
| over 20odd cpu architectures.
|
| It's the same build system for all of them.
| tdiff wrote:
| Works for numerous projects which "run the world".
|
| Everyone know the system is brittle, but somehow manage
| to handle it.
| affyboi wrote:
| > Works for whom?
|
| FAANG, hedge funds/HFT, game studios
| guappa wrote:
| apt install xxxxx-dev
| runevault wrote:
| When I've dabbled in C++ recently it has felt like using
| CMake fetching github repos has been the least painful thing
| I've tried (dabbled in vcpkg and conan a bit), since most
| libraries are cmake projects.
|
| I am no expert so take it with a grain of salt, but that was
| how it felt for me.
| kylereeve wrote:
| Do you have CMake actually run `git clone`, or do you clone
| separately and point CMake at the `FIND_X` files?
| runevault wrote:
| I was using fetch content or the like, there is a package
| that comes after a certain version of cmake where you can
| tell it this is a git repo and it handles all that for
| you. It has been a few months since I did this so I don't
| remember the details fully
| morsecodist wrote:
| Agreed. I have had almost the same experience. The package
| management and building alone makes Rust worth it for me.
| ValtteriL wrote:
| Felt the same pain with vcpkg. Ended up using OS packages and
| occasionally simply downloading a pure header based
| dependency.
|
| With Nix, the package selection is great and repackaging is
| fairly straight forward.
| andrepd wrote:
| Lack of pattern matching and move only types means you
| physically cannot code in C++ as you would in Rust, even
| ignoring all the memory safety stuff.
| mattgodbolt wrote:
| Wow that guy, eh? He seems to turn up everywhere :D
| oconnor663 wrote:
| This account's been pretending to be Matt Godbolt since 2014.
| Knows him better than he knows himself. Absolute commitment to
| the bit.
| 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.
| pjmlp wrote:
| Yes, from safety point of view Rust is much better option,
| however from the ecosystems I care about (language runtimes and
| GPU coding), both professionally and as hobby, C++ is the
| systems language to go, using Rust in such contexts would
| require me to introduce extra layers and do yak shaving instead
| of the actual problem that I want to code for.
| spyrja wrote:
| Well, precisely. Such is the price of "general-purpose
| safety". The biggest irony IMO is that the security features
| of Rust are so often skirted in the name of speed,
| efficiency, etc, with the end result being a program which
| isn't much more secure than its C++ equivalent. -\\_(tsu)_/-
| 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...
| conradludgate wrote:
| As long as the wrapper is marked as transparent, it's safe to
| transmute the wrappers.
|
| If you don't want unsafe, you can make use of this safe derive:
| https://docs.rs/bytemuck/latest/bytemuck/trait.TransparentWr...
| simpaticoder wrote:
| I don't get it. Isn't this a runtime problem and not a compile-
| time problem? buy() or sell() is going to be called with dynamic
| parameters at runtime, in general. That is, calls with concrete
| values are NOT going to be hard-coded into your program. I would
| write the function to assert() invariants within the function,
| and avoid chasing compile-time safety entirely. If parameter
| order was a concern, then I'd modify the function to take a
| struct, or similar.
| brundolf wrote:
| > Isn't this a runtime problem and not a compile-time problem?
| buy() or sell() is going to be called with dynamic parameters
| at runtime, in general.
|
| Yes, but the strength of Rust's type system means you're forced
| to handle those bad dynamic values up front (or get a crash, if
| you don't). That means the rest of your code can rest safe,
| knowing exactly what it's working with. You can see this in
| OP's parsing example, but it also applies to database clients
| and such
| simpaticoder wrote:
| What if the valid input for quantity must be greater than 0?
| A reasonable constraint, I think. The OP's example is
| contrived to line up with Rust's built-in types, and ignores
| the general problem.
| siev wrote:
| Rust also has the standard NonZero<T> type for that use
| case.
| brundolf wrote:
| It's a common fallacy to equate "there's a limit to how
| much we can guarantee" with "guaranteeing anything is a
| waste of time". Each guarantee we can make eliminates a
| whole class of possible bugs
|
| That said, Rust also makes it very easy to define your own
| types that can only be constructed/unpacked in limited
| ways, which can enforce special constraints on their
| contents. And it has a cultural norm of doing this in the
| standard library and elsewhere
|
| Eg: a sibling poster noted the NonZero<T> type. Another
| example is that Rust's string types are guarantees to
| always contain valid UTF-8, because whenever you try and
| convert a byte array into a string, it gets checked and
| possibly rejected.
| brundolf wrote:
| Wait, Godbolt is someone's _name_?
| frankwiles wrote:
| Yes and he's a really cool nice guy to boot!
| brundolf wrote:
| I almost want to call this nominative determinism
|
| I always thought it was called godbolt because it's like...
| Zeus blowing away the layers of compilation with his cosmic
| power, or something. Like it's a herculean task
| tialaramex wrote:
| Another example: eBay is because its founders solo
| consulting business "Echo Bay Technology Group" owned
| ebay.com and so when he built his auction web site on that
| domain everybody just called the auction site "eBay"
| anyway.
| socalgal2 wrote:
| I already hated C++ (having written 100s of thousands of lines of
| it in games and at FAANG)
|
| I'd be curious to know what if any true fixes are coming down the
| line.
|
| This talk: "To Int or to Uint, This is the Question - Alex
| Dathskovsky - CppCon 2024"
| https://www.youtube.com/watch?v=pnaZ0x9Mmm0
|
| Seems to make it clear C++ is just broken. That said, and I wish
| he'd covered this, he didn't mention if the flags he brings up
| would warn/fix these issues.
|
| I don't want a C++ where I have to remember 1000 rules and if I
| get one wrong my code is exploitable. I want a C++ where I just
| can't break the rules except when I explicitly opt into breaking
| them.
|
| speaking of which, according to another C++ talk, something like
| 60% of rust crates are dependent on unsafe rust. The point isn't
| to diss rust. The point is that a safe C++ with opt into unsafe
| could be similar to rust's opt into unsafe
| eslaught wrote:
| There has been talk of new language frontends for C++:
|
| Cpp2 (Herb Sutter's brainchild):
| https://hsutter.github.io/cppfront/
|
| Carbon (from Google): https://github.com/carbon-
| language/carbon-lang
|
| In principle those could enable a safe subset by default, which
| would (except when explicitly opted-out) provide similar safety
| guarantees to Rust, at least at the language level. It's still
| up to the community to design safe APIs _around_ those
| features, even if the languages exist. Rust has a massive
| advantage here that the community built the ecosystem with
| safety in mind from day 1, so it 's not just the language
| that's safe, but the APIs of various libraries are often
| designed in an abuse-resistant way. C++ is too much of a zoo to
| ever do that in a coherent way. And even if you wanted to, the
| "safe" variants are still in their infancy, so the foundations
| aren't there yet to build upon.
|
| I don't know what chance Cpp2 or Carbon have, but I think you
| need something as radical as one of these options to ever stand
| a chance of meaningfully making C++ safer. Whether they'll take
| off (and before Rust eats the world) is anyone's guess.
| aw1621107 wrote:
| I don't think Carbon is a C++ frontend like cppfront. My
| impression is that cppfront supports C++ interop by
| transpiling/compiling to C++, but Carbon compiles straight to
| LLVM and supports C++ interop through built-in language
| mechanisms.
| aw1621107 wrote:
| > speaking of which, according to another C++ talk, something
| like 60% of rust crates are dependent on unsafe rust.
|
| It's probably not the source of the stats you had in mind since
| it's discussing something slightly different, but the Rust
| Foundation built a tool called Painter [0] for this kind of
| analysis. According to that [1]:
|
| > As of May 2024, there are about 145,000 crates; of which,
| approximately 127,000 contain significant code. Of those
| 127,000 crates, 24,362 make use of the unsafe keyword, which is
| 19.11% of all crates. And 34.35% make a direct function call
| into another crate that uses the unsafe keyword. Nearly 20% of
| all crates have at least one instance of the unsafe keyword, a
| non-trivial number.
|
| > Most of these Unsafe Rust uses are calls into existing third-
| party non-Rust language code or libraries, such as C or C++.
|
| To be honest, I would have expected that 60% number to be
| higher if it were counting unsafe _anywhere_ due to unsafe in
| the stdlib for vocabulary types and for (presumably) common
| operations like iterator chains. There 's also a whole other
| argument that the hardware is unsafe so _all_ Rust code will
| depend on unsafe somewhere or another to run on actual
| hardware, but that 's probably getting a bit into the weeds.
|
| [0]: https://github.com/rustfoundation/painter
|
| [1]: https://rustfoundation.org/media/unsafe-rust-in-the-wild-
| not...
| Ygg2 wrote:
| > there's also a whole other argument that the hardware is
| unsafe so all Rust code will depend on unsafe somewhere or
| another to run on actual hardware, but that's probably
| getting a bit into the weeds.
|
| That's not going into the weeds, by that logic (Nirvana
| fallacy) no language is safe, you're going to die, so why
| bother about anything? Just lie down and wait for bugs to eat
| you.
| aw1621107 wrote:
| Perhaps I got the quip wrong. I was basically trying to
| reference discussions I've seen elsewhere along the lines
| of "Rust is not actually memory-safe because it needs
| unsafe", sometimes followed by the argument you outlined.
| Those discussions can get a bit involved and I don't think
| this is a good time/place for them, so I more or less just
| wanted to reference it without spending much time/words on
| actually delving into it.
| eptcyka wrote:
| Memory allocation is unsafe, so any container in the stdlib
| end up using unsafe at some point. This does not mean that
| safe Rust is useless without unsafe - the utility of Rust is
| that it allows one to create sage interfaces around an unsafe
| construct.
| aw1621107 wrote:
| Right, I totally agree. I suppose I was kind of trying to
| express a bit of confusion at the 60% number due to the
| lack of specifics of what it encompasses (e.g., how far
| down the dependency chain is that number looking?), unlike
| the stats I quoted.
| xarope wrote:
| This reminds me of SQL's constraints, or pydantic's custom types
| and validators, which can validate that a value should be an int,
| and between 0-999, and not exceed, e.g. -1 or 1000.
|
| pydantic is a library for python, but I'm not aware of anything
| similar in rust or golang that can do this yet? (i.e. not just
| schema validation, but value range validation too)
| kstrauser wrote:
| The semantics are a little different in that you have to
| explicitly call .validate(), but Rust's "validator" crate[0]
| feels a lot like Pydantic to me.
|
| [0]https://docs.rs/validator/latest/validator/
| nailer wrote:
| > Before we go any further, let me just say he acknowledges
| floating point is not right for price and later talks about how
| he usually deals with it. But it makes for a nice example, bear
| with us.
|
| OK but "this makes for a nice example" is silly, given that the
| only reason the example throws an error is that you used a float
| here, when both `quantity` and `price` would have been ints.
| error[E0308]: arguments to this function are incorrect
| --> order/order-1.rs:7:5 | 7 |
| send_order("GOOG", false, 1000.00, 100); // Wrong |
| ^^^^^^^^^^ ------- --- expected `f64`, found
| `{integer}` | |
| | expected `i64`, found `{float}`
|
| I love Rust, but this is artificial.
| tialaramex wrote:
| Why are they "ints" ? One of the first things we realise, in
| both C++ and Rust, is that we mostly don't want these
| primitives like "int", we want our own user defined types, and
| Rust is better at that _in practice_.
|
| In the C and C++ people tend to actually write the file
| descriptor will be an int, and the timeout will be an int, and
| the user account number will be an int, and the error code will
| be an int... because the language doesn't help much when you
| don't want that.
|
| In the Rust people actually write the file descriptor will be
| an OwnedFd (from the stdlib) and the timeout will be a Duration
| (from the stdlib), and user account number might be their own
| AcctNo and that error code is maybe MyCustomError
|
| This is a language ethos thing, C++ got string slices _after_
| Rust despite the language being much older. String slices which
| are a really basic central idea, but eh, C++ programmers a
| decade ago just had char * pointers instead and tried not to
| think about it too much. Still today plenty of C++ APIs don 't
| use string slices, don't work with a real duration type, and so
| on. It's technically _possible_ but the language doesn 't
| encourage this.
|
| What C++ does encourage is magic implicit conversion, as with
| this f64 versus i64 case.
| nailer wrote:
| Hah, it's interesting - I program Rust mainly for resource
| constrained environments and everyone uses primitives. This
| was a good insight into how other people are thinking.
| TnS-hun wrote:
| Examples miss angle brackets. For example:
| template explicit Quantity(T quantity) : m_quantity(quantity) {
| sendOrder("GOOG", false, Quantity(static_cast(atoi("-100"))),
| time4tea wrote:
| aka use Tiny Types, they will help you.
|
| Been true in all statically typed languages for decades!
|
| It's good advice.
| choeger wrote:
| All this has been known in the PL design community for _decades_
| if not half a century by now.
|
| Two things are incredibly frustrating when it comes to safety in
| software engineering:
|
| 1. The arrogance that "practitioners" have against "theorists"
| (everyone with a PhD in programming languages)
|
| 2. The slowness of the adoption of well-tested and thoroughly
| researched language concepts (think of Haskell type classes, aka,
| Rust traits)
|
| I like that Rust can pick good concepts and design coherent
| language from them without inventing its own "pragmatic" solution
| that breaks horribly in some use cases that some "practitioners"
| deem "too theoretical."
| Ygg2 wrote:
| > I like that Rust can pick good concepts and design coherent
| language from them without inventing its own "pragmatic"
| solution that breaks horribly in some use cases that some
| "practitioners" deem "too theoretical."
|
| I've thought Rust picked some pretty nifty middle ground. On
| one side, it's not mindfucking unsafe like C. It picked to
| remove a set of problems like memory safety. On the other side,
| Rust didn't go for the highest of theoretical grounds. It's not
| guaranteeing much outside of it, and it also relies a bit on
| human help (unsafe blocks).
| eptcyka wrote:
| As per the article, Rust has benefits beyond the ones
| afforded by the borrow checker.
| Ygg2 wrote:
| Sure, but it is pragmatic in other ways as well :)
|
| It takes ADT, but not function currying, and so on.
| jeffparsons wrote:
| I've occasionally wondered about the lack of currying in
| Rust; it feels like something that can be done
| mechanically at compile time, so why not support it?
| Perhaps to do it cleanly (without more magical privileged
| functions in core) would require variadic generics?
| bunderbunder wrote:
| You can absolutely curry functions in Rust. You just have
| to do it manually because there's no syntactic sugar for
| it.
|
| I think that's a good thing. A curried function is a
| function that takes one argument, and returns a closure
| that captures the argument. That closure might itself
| take another argument, and return a new closure that
| captures both the original argument and the second one.
| And so on ad infinitum. How ownership and borrowing works
| across that chain of closures could easily become a
| touchy issue, so you probably want to be making it as
| explicit as possible.
|
| Or perhaps better yet, find an easier way to accomplish
| the same task. Maybe use a struct to explicitly carry the
| arguments along until you're ready to call the function.
| skybrian wrote:
| This isn't Rust-specific, but one reason a language
| designer might deliberately not implement currying is
| that it makes function call sites a bit harder to read.
| You need to already know how many arguments a function
| takes to make sense of the call's return type.
| andrepd wrote:
| I don't think currying is that big a deal, it's just
| syntactic sugar that might or might not make things
| easier to read, unlike ADTs or closures which _are_
| important core concepts.
|
| I'd love to have a syntax like {
| foo(%1, bar) }
|
| standing for |x| { foo(x, bar) }
|
| though. I'm not aware of any language that has this!
| Munksgaard wrote:
| Elixir has this, which is close:
| &foo(&1, bar)
| tcfhgj wrote:
| Powershell has this, why do you like this?
| masijo wrote:
| Clojure has this user=> (#(println %1
| %2) "Hello " "Clojure") Hello Clojure
| tormeh wrote:
| I'd argue that currying is actively harmful (I suspect
| you agree). I've seen functions take one argument in one
| source file, and the next argument in another source
| file. In JavaScript no less. Horrendous stuff. One of my
| most hated anti-features, along with Scala's implicits.
| These kinds of features are mostly misused rather than
| used.
| bunderbunder wrote:
| From what I've seen, partial application tends to be used
| for utmost good in dialects of ML, and utmost evil most
| everywhere else. Chaotic neutral in R/tidyverse.
| tmoertel wrote:
| _Mathematica_ has this: In[1]: Map((1
| + #1)&, {a, b, c}) Out[1]: {a + 1, b + 1, c + 1}
|
| See https://reference.wolfram.com/language/ref/Function.h
| tml.en for the full story.
| GrantMoyer wrote:
| C++ has this[1], kind of, but please don't actually use
| it.
|
| > for_each(a.begin(), a.end(), std::cout << _1 << ' ');
|
| Also, Scala has something similar as a first class
| feature[2].
|
| [1]: https://www.boost.org/doc/libs/1_88_0/doc/html/lambd
| a.html
|
| [2]: https://scala-
| lang.org/files/archive/spec/3.4/06-expressions...
| icen wrote:
| In K the arguments are named x y z by default, so you
| just write: { foo[x, bar] }
| cobbal wrote:
| swift has this as well { foo($0, bar) }
| sanderjd wrote:
| Yep, this article is a good example of one way that c++ is bad,
| but it's not really a great example of rust being particularly
| good; many other languages support this well. I'm very glad
| Rust is one of those languages though!
| groos wrote:
| I had the same thought - what Matt's examples required was
| strong typing and that has existed for very long time outside
| of the C family world.
| blub wrote:
| If the practitioners haven't adopted what you're offering for
| 50+ years, that thing can't be good.
|
| Rust is also struggling with its "too theoretical" concepts by
| the way. The attempts of the community to gaslight the
| practitioners that the concepts are in fact easy to learn and
| straightforward are only enjoying mild success, if I may call
| it that.
| db48x wrote:
| I disagree. The advertising and hype pushing people to use
| C++ is insane. There are hundreds of magazines that exist
| solely to absorb the advertising budget of Microsoft (and to
| a lesser extent Intel). Hundreds of conferences every year.
| You could be writing code in ML at your startup with no
| complaints and demonstrable success but as soon as your
| company gets big enough to send your CEO to an industry
| conference you'll be switching to C++. The in-flight magazine
| will extol the benefits of MSVC, speakers like Matt Godbolt
| will preach Correct by Construction in C++, etc, etc. By the
| time he gets back he's been brainwashed into thinking that
| C++ is the next best thing.
| fsloth wrote:
| "If the practitioners haven't adopted what you're offering
| for 50+ years, that thing can't be good."
|
| I don't think what features are popular in C++ is good
| indication of anything. The language is good only due to the
| insane amounts of investment to the ecosystem, not because of
| the language features due to design.
|
| For an industrial language inventory of "nice features to
| have" F# and C# are mostly my personal gold standard.
|
| "Too theoretical" is IMO not the correct lens to use. I would
| propose as a better lens a) which patterns you often use b)
| how to implement them in language design itself.
|
| A case in point is the gang-of-four book. It mostly gives
| names to things in C++ that are language features in better
| languages.
| senderista wrote:
| The GoF book uses Smalltalk for the examples as well as
| C++, and Smalltalk is about the most expressive imperative
| language you could ask for.
| ordu wrote:
| _> If the practitioners haven't adopted what you're offering
| for 50+ years, that thing can't be good._
|
| I wouldn't trust opinion of practitioners. Not after they
| have chosen javascript and, God forbid, PHP. Practitioners
| choose not what is inherently good, but what is popular. It
| is a very practical choice that brings a lot of benefits, so
| it is just practitioners being practitioners. It can be good
| or bad, I don't care now, it doesn't matter for my argument.
| The issue that a good thing can be overlooked by
| practitioners for decades, because there is nothing popular
| giving them this thing.
| j_w wrote:
| But being popular can be very important for practitioners.
| How else are you going to hire other practitioners?
| bigbuppo wrote:
| It's weird that this sort of debate around C++ often leaves out
| the fact that many of the problems with C++ were known _before_
| C++ even existed. Outside of a few specific buckets, there is
| no reason to use C++ for any new projects, and really, there
| never has been. If you can 't stomach Rust for some reason, and
| I'm one of those people, there are plenty of choices out there
| without all the pitfalls of C++ or C.
| ivmaykov wrote:
| > If you can't stomach Rust for some reason, and I'm one of
| those people, there are plenty of choices out there without
| all the pitfalls of C++ or C.
|
| Unless you are doing embedded programming ...
| fsloth wrote:
| I think embedded is one of the specific buckets.
|
| You target the compiler your client uses for their
| platform. There is very little choice there.
| karel-3d wrote:
| Well, if you don't want to confuse parameters, you should use
| Objective-C.
|
| You would do
|
| [orderbook sendOrderWithSymbol:"foo" buy:true quantity:100
| price:1000.00]
|
| Cannot confuse that!
|
| (I never used swift, I think it retains this?)
| feverzsj wrote:
| The C++ code is just pure nonsense. If you really want to be
| fool-proofing, C++ offers concept, which is far superior to what
| rust offers.
| mansoor_ wrote:
| FYI newer builds of GCC have this functionality.
| globalnode wrote:
| My admittedly uninformed impression of Rust is that its a lot
| like Go (in spirit?), a language invented to shepherd novice
| programmers into not making mistakes with resource usage.
|
| I imagine faceless shameless mega-corps with thousands of Rust/Go
| peons coding away on the latest soulless business apps. Designed
| to funnel the ignorant masses down corridors of dark pattern
| click bait and confusing UX.
|
| Having exposed my biases, happy to be proven wrong. Why are game
| studios still using C++? Because that's the language game
| programmers know and feel comfortable with? Or some other reason?
|
| Embedded is still C, games are C++, scientific and data are
| Python and R (I'm talking in general here). What is the niche for
| Rust?
| simonask wrote:
| Novice programmers will take longer to be productive in Rust
| compared to Go. Rust primarily improves the productivity of
| people who know what they are doing, because it gives them much
| better tools to manage complexity.
|
| Games are written in C++ because game engines and tooling have
| person-centuries of work poured into them. Reimplementing
| Unreal Engine in Rust would require another few person-
| centuries of work, which is an investment that doesn't really
| make sense. Economically, dealing with the shortcomings of C++
| is much, much cheaper.
|
| But Rust is definitely encroaching in all of these areas.
| Embedded Rust is doing great, scientific Rust is getting there
| (check pola.rs). Rust is an obvious candidate for the next big
| game engine, and it is already quite viable for indie
| undertakings, though it is still early days.
| amai wrote:
| Though in the future people might simply ask an AI to convert
| a codebase from C++ to Rust.
| gwd wrote:
| > novice programmers
|
| I think Rust has too high a learning curve, and too many
| features, for novice programmers in general.
|
| > Embedded is still C, games are C++, scientific and data are
| Python and R (I'm talking in general here). What is the niche
| for Rust?
|
| Rust has already made huge inroads in CLIs and TUIs, as far as
| I can tell. Embedded is a slow-moving beast by design, but it
| seems to me (as someone in an adjacent area) that it could be a
| big win there, particularly in places that need safety
| certification.
|
| All the stories of people using Rust for game development are
| about people who tried it and find that it doesn't fit: It
| makes experimentation and exploration slow enough that the
| reduction in minor bugs in game logic isn't really worth it.
| notimetorelax wrote:
| Those mega corps that you talk about use C++ too. It's just a
| false dichotomy argument you're making.
| globalnode wrote:
| True enough, I was going down a path there and got a little
| excited :S
| chickenbuckcar wrote:
| Economic inertia alone can already enough.
|
| Numpy use C/C++ because BLAS use C/C++ Torch originally use
| Lua, then switch to Python because popularity
| imtringued wrote:
| The quality of AMD's software stack speaks for itself. It's all
| C++ and the quality is exactly poor as you'd expect.
| Havoc wrote:
| Go is seeing more traction in the web space - api backends and
| other stuff that needs lots of concurrency like that. Seen as
| easier to learn than rust but not quite as fine grained low
| level control.
|
| Rust is a bit more systems focused for low level stuff. See
| inclusions in the Linux kernel. Also seeing some traction in
| the WASM space given that it's not GC
|
| They're both quite versatile though so above are pretty gnarly
| generalisations.
|
| Zig is in a similar space as these
| TinkersW wrote:
| Dunno about this, the example C++ code is so obviously bad that I
| had no desire to watch the video.
|
| Creating strong types for currency seems like common sense, and
| isn't hard to do. Even the Rust code shouldn't be using basic
| types.
| jk3000 wrote:
| Somehow this is the pattern when comparing C++ to Rust: write
| outrageously bad C++ in the first place, then complain about
| it.
| quietbritishjim wrote:
| I always enjoy reading articles like this. But the truth is,
| having written several 100s of KLOC in C++ (i.e., not an enormous
| amount but certainly my fair share) I just almost never have
| problems with this sort accidental conversion in practice.
| Perhaps it might trip me up occasionally, but will be noticed by
| literally just running the code once. Yes, that is an extra
| hurdle to trip over and resolve but that is trivial compared to
| the alternative of creating and using wrapper types - regardless
| of whether I'm using Rust or C++. And the cost of visual noise of
| wrapper types, already higher just at the writing stage, then
| continues to be a cost every time you read the code. It's just
| not worth it for the very minor benefit it brings.
|
| (Named parameters would definitely be great, though. I use little
| structs of parameters where I think that's useful, and set their
| members one line at a time.)
|
| I know that this is an extremist view, but: I feel the same way
| about Rust's borrow checker. I just very rarely have problems
| with memory errors in C++ code bases with a little thought
| applied to lifetimes and use of smart pointers. Certainly,
| lifetime bugs are massively overshadowed by logic and algorithmic
| bugs. Why would I want to totally reshape the way that I code in
| order to fix one of the _least_ significant problems I encounter?
| I actually wish there were a variant of Rust with all its nice
| clean improvements over C++ _except_ for lifetime annotations and
| the borrow checker.
|
| Perhaps this is a symptom of the code I tend to write: code that
| has a lot of tricky mathematical algorithms in it, rather than
| just "plumbing" data between different sources. But actually I
| doubt it, so I'm surprised this isn't a more common view.
| spacechild1 wrote:
| > Why would I want to totally reshape the way that I code in
| order to fix one of the least significant problems I encounter?
|
| I feel the same. Rust certainly has many nice properties and
| features, but the borrow checker is a huge turn-off for me.
| viraptor wrote:
| > but will be noticed by literally just running the code once.
|
| I assure you that's not the case. Maybe you didn't make that
| mistake, but if you did I'm sure it sometimes went unnoticed.
| I've found those issues in my code and in other projects.
| Sometimes they even temporarily don't matter, because someone
| did a func(CONST, 0) instead of func(0, CONST) and it turns out
| CONST is 0 - however the next person gets a crash because they
| change 0 to 1. A lot of similar issues come from the last line
| effect https://medium.com/@Code_Analysis/the-last-line-
| effect-7b1cb... and can last for years without being noticed.
| lmm wrote:
| > I just almost never have problems with this sort accidental
| conversion in practice.
|
| 95% of C++ programmers claim this, but C++ programs continue to
| be full of bugs, and they're usually exactly this kind of dumb
| bug.
|
| > will be noticed by literally just running the code once.
|
| Maybe. If what you're doing is "tricky mathematical
| algorithms", how would you even know if you were making these
| mistakes and not noticing them?
|
| > the cost of visual noise of wrapper types, already higher
| just at the writing stage, then continues to be a cost every
| time you read the code. It's just not worth it for the very
| minor benefit it brings.
|
| I find wrapper types are not a cost but a benefit for
| readability. They make it so much easier to see what's going
| on. Often you can read a function's prototype and immediately
| know what it does.
|
| > Certainly, lifetime bugs are massively overshadowed by logic
| and algorithmic bugs.
|
| Everyone claims this, but the best available research shows
| exactly the opposite, at least when it comes to security bugs
| (which in most domains - perhaps not yours - are vastly more
| costly): the most common bugs are still the really dumb ones,
| null pointer dereferences, array out of bounds, and double
| frees.
| blub wrote:
| It's the sociology of software development.
|
| The guild of software developers has no real standards, no
| certification, no proven practices outside <book> and <what
| $company is doing> while continuing to depend on the whims of
| project managers, POs and so-caled technical leaders and
| others which can't tell quality code from their own ass.
|
| There's usually no money in writing high-quality software and
| almost everything in a software development project conspires
| against quality. Languages like Rust are a desperate attempt
| at fixing that with technology.
|
| I guess it works, in a way, but these kind of blog posts just
| show us how inept most programmers are and why the Rust band-
| aid was needed in the first place.
| tormeh wrote:
| Maybe. But I wouldn't diss better languages, linters, and
| other tool inprovements. These systematically increase
| quality at very low cost. It boggles my mind that the whole
| industry is not falling over itself to continuously embrace
| better tools and technology.
| benced wrote:
| The whole industry is, the C++ ecosystem is just not.
| eftychis wrote:
| This. The industry is a hot-pot of gut feelings/seat of my
| pants mixed with true engineering and mathematical rigor.
|
| It is all hit or miss. Everyone claims they do high-
| quality, _critical_ software in public, while in private,
| they claim the opposite, that they are fast and break
| things, and programming is an art, not math.
|
| And then you have venture capital firms now pushing "vibe
| coding."
|
| Software development is likely the highest variance
| engineering space, sometimes and in some companies, not
| even being engineering, but "vibes."
|
| It is interesting how this is going to progress forward.
| Are we going to have a situation like the Quebec Bridge
| [https://colterreed.com/the-failed-bridge-that-inspired-a-
| sim...]. The Crowdstrike incident taking down the whole
| airspace proved that is not enough. Market hacks in
| "decentralized exchanges," the same. Not sure where we are
| heading.
|
| I guess we are waiting for some catastrophe that will have
| some venture capital liable for the vibe coding, and then
| we will have world wide regulation pushed on us.
| davemp wrote:
| My current project is a huge C++ physics sim written over 15+
| years. The most common and difficult to diagnose bug I've
| found is unit conversation mistakes. We likely wouldn't even
| find them if we didn't have concrete data to compare against.
| j16sdiz wrote:
| There are a few unit library in C++.
|
| Type checking in compile time is do-able with templates,
| even better with constexpr.
|
| The problem is, of course, each library have its own set of
| rules and they won't interop with each other.
| bluGill wrote:
| I wrote such a type library myself, and it worked great.
| However we eventually realized it was the wrong answer
| because you so commonly want to display that thing and
| nobody wanted to write each widget to have a different
| api for each other the thousands of different types in my
| library.
|
| The current system is a runtime system which has one
| type, and you set what the unit system is in the
| constructor. However it means adding a meter to a gallon
| is a runtime error.
| blub wrote:
| This article presents something I'd expect competent C++
| programmers with a few years of experience to know.
|
| Unfortunately, many programmers are not competent. And the
| typical modern company will do anything in its power to
| outsource to often the lowest bidder, mismanage projects and
| generally reduce quality to the minimum acceptable to make
| money. That's why one _needs_ tools like Rust, Java,
| TypeScript, etc.
|
| Unfortunately, Rust is still too hard for the average
| programmer, but at least it will hit them over the hands with a
| stick when they do something stupid. Another funny thing about
| Rust is that it's attracting the functional
| programming/metaprogramming astronauts in droves, which is at
| odds with it being the people's programming language.
|
| I still don't think it's a valuable skill. Before it was lack
| of jobs and projects, which is still a problem. Now it's the
| concern that it's as fun as <activity>, except in a
| straitjacket.
| devnullbrain wrote:
| >code that has a lot of tricky mathematical algorithms in it,
| rather than just "plumbing" data between different sources
|
| Your hierarchy is backwards. Borrowing for algorithmic code is
| easy, it's for writing libraries that can be used by others
| where it's hard. Rust lets you - _makes you_ - encode in in the
| API in a way C++ can 't yet express.
|
| > I just very rarely have problems with memory errors in C++
| code bases with a little thought applied to lifetimes and use
| of smart pointers
|
| If these are sparing you C++ bugs but causing you to struggle
| with the borrow checker, it's because you're writing code that
| depends on constraints that you can't force other contributors
| (or future you) to stick to. For example, objects are thread-
| unsafe by default. You can use expensive locks, or you can pray
| that nobody uses it wrong, but you can't _design_ it so it can
| only be used correctly and efficiently.
| kevincox wrote:
| I had a friend who noticed that people were often mixing up the
| arguments to some std constructor (I think it was string with a
| char and other integer argument getting swapped.) He searched
| across Google's codebase and found many (I don't remember the
| exact number) cases of this, many that he could confirm to be
| real bugs. He spent months fixing them and I think eventually
| got some check added to prevent this in the future.
|
| So this definitely isn't some theoretical problem. I wouldn't
| even be surprised if you had made this mistake just hadn't
| noticed.
| humanrebar wrote:
| I understand this concern, but at the same time it's not hard
| to write clang-query statements for the ones you care about.
| Sometimes it is even a regex! And it's not too expensive to
| upstream universally relevant checks to clang-tidy.
|
| The main problem is that too many C++ engineers don't do any
| of that. They have some sort of learned helplessness when it
| comes to tooling. Rust for now seems to have core engineers
| in place that will do this sort of on behalf of everyone
| else. Language design aside, if it can find a way to sustain
| that kind of solid engineering, it will be hard to argue
| against.
| zombot wrote:
| Types are a catastrophe in C++ that cannot be fixed, no matter
| how much cruft you bolt on after the fact. It's time to leave
| this dinosaur and use a sane language.
| monkeyelite wrote:
| This article compares onlye one specific feature of C++ with Rust
| - integral type conversions.
| tialaramex wrote:
| All of C++ has implicit type conversions. The language even has
| a keyword (explicit) to try to reign this in because it's so
| obviously a bad idea.
|
| In Rust what they'd do if they realised there's a problem like
| this is make explicit conversion the default, with a language
| Edition, and so within a few years just everybody is used to
| the improved language. In C++ instead you have to learn to
| write all the appropriate bugfix keywords all over your
| software, forever.
| monkeyelite wrote:
| > because it's so obviously a bad idea.
|
| Agreed. The history here is compatibility with C type
| conversion.
|
| I just expected a more compelling Rust /C++ comparison but we
| got an emphasis of a poorly designed feature which the
| standard has taken steps to improve already.
| tialaramex wrote:
| No, implicit conversion is a deliberate C++ feature and no
| analog existed in C. Like a lot of awful things about C++
| this is their own choice and it's frustrating that they try
| to blame C for their choices.
|
| In C++ when we define a class Foo (a thing which doesn't
| exist in C) and we write a constructor Foo(Bar x) (which
| doesn't exist in C) which takes a single parameter [in this
| case a Bar named x], that is _implicitly_ adopted as a
| conversion for your new user defined type and _by default_
| without any action on your part the compiler will just
| invoke that constructor to make a Bar into a Foo whenever
| it thinks that would compile.
|
| This is a bad choice, and it's not a C choice, it's not
| about "compatibility".
| monkeyelite wrote:
| > No, implicit conversion is a deliberate C++ feature and
| no analog existed in C
|
| No.
|
| > it's not a C choice, it's not about "compatibility".
|
| One of the design of C++ classes is that you can create a
| class as powerful as int - you can't do that without
| implicit conversion.
| tialaramex wrote:
| It would have been perfectly possible - not to mention
| obviously better - to make people actually _write what
| they meant_ when defining the new type and this has no
| impact on the dubious priority of being able to make your
| own int type by gifiting your type implicit conversions
| in use _if that 's what you want_ which it often is not.
|
| This is just another thing on the deep pile of wrong
| defaults in C++.
| monkeyelite wrote:
| Yes, it's a wrong default problem.
| amai wrote:
| ADA has solved these issues since the 80s:
|
| https://learn.adacore.com/courses/Ada_For_The_CPP_Java_Devel...
| DrBazza wrote:
| The implicit problem here (pun intended) in the given examples
| are implicitness vs. explicitness.
|
| Rust chose (intentionally or otherwise) to do the opposite of the
| many things that C++ does, because C++ does it wrong. And C++
| does it wrong because we didn't know any better at the time, and
| the world, pre-internet, was much less connected. Someone had to
| do it first (or first-ish).
|
| The main thing I like about Rust is the tooling. C++ is death by
| a thousand build systems and sanitizers.
| nmeofthestate wrote:
| If I tried to repro this problem in C++ code I believe it would
| fail clang tidy checks because of the implicit numeric casts.
| Horffupolde wrote:
| Would this also work for Zig?
| jovial_cavalier wrote:
| Hey, check this out:
|
| #include <iostream>
|
| struct Price { double x; };
|
| struct Quantity { int x; };
|
| void sendOrder(const char *symbol, bool buy, Quantity quantity,
| Price price) { std::cout << symbol << " " <<
| buy << " " << quantity.x << " " << price.x <<
| std::endl;
|
| }
|
| int main(void) { sendOrder("GOOG", false,
| Quantity{100}, Price{1000.00}); // Correct
| sendOrder("GOOG", false, Price{1000.00}, Quantity{100}); //
| compiler error
|
| }
|
| If you're trying to get it to type check, you have to make a type
| first.
|
| I don't appreciate these arguments, and view them as
| disingenuous.
| legobmw99 wrote:
| That still has the issue of Quantity{-100} being a-ok as far as
| the compiler is concerned, but there are other things one can
| do (as the article alludes to).
|
| My reading of this article wasn't to say that these things are
| impossible in C++, just that they're not the default or the
| first thing you try as a beginner is perfectly wrong.
| bunderbunder wrote:
| This is actually the point where Rust starts to frustrate me a
| little bit.
|
| Not because Rust is doing anything wrong here, but because the
| first well-known language to really get some of these things
| right also happens to be a fairly low-level systems language with
| manual memory management.
|
| A lot of my colleagues seem to primarily be falling in love with
| Rust because it's doing a good job at some basic things that have
| been well-known among us "academic" functional programming nerds
| for decades, and that's good. It arguably made inroads where
| functional programming languages could not because it's really
| more of a procedural language, and that's also good. Procedural
| programming is a criminally underrated and misunderstood
| paradigm. (As much as I love FP, that level of standoffishness
| about mutation and state isn't any more pragmatic than OOP being
| so hype about late binding that every type must support it
| regardless of whether it makes sense in that case.)
|
| But they're also thoroughly nerdsniped by the borrow checker. I
| get it, you have to get cozy with the borrow checker if you want
| to use Rust. But it seems like the moral opposite of sour grapes
| to me. The honest truth is that, for most the software we're
| writing, a garbage collected heap is _fine_. Better, even.
| Shared-nothing multithreading is _fine_. Better, even.
|
| So now we're doing more and more things in Rust. Which I
| understand. But I keep wishing that I could also have a Rust-like
| language that just lets me have a garbage collector for the 95%
| of my work where the occasional 50ms pause during run-time just
| isn't a big enough problem to justify a 50% increase in
| development and maintenance effort. And then save Rust for the
| things that actually do need to be unmanaged. Which is maybe 5%
| of my actual work, even if I have to admit that it often feels
| like 95% of the fun.
| legobmw99 wrote:
| Based on the rest of your comment I suspect you're already
| familiar, but a decent candidate for "Rust with a GC" is OCaml,
| the language the first Rust compiler was written in.
| bunderbunder wrote:
| It's close. Perhaps there's an interesting conversation to be
| had about why OCaml hasn't taken over the world the way Rust
| has.
|
| The toolchain might be a first candidate. Rust's toolchain
| feels so very modern, and OCaml's gives me flashbacks to late
| nights trying to get my homework done on the department's HP-
| UX server back in college.
| sn9 wrote:
| Can I interest you in some Moonbit?
|
| https://www.moonbitlang.com/blog/first-announce
| Starlevel004 wrote:
| > Not because Rust is doing anything wrong here, but because
| the first well-known language to really get some of these
| things right also happens to be a fairly low-level systems
| language with manual memory management.
|
| It also has half implementations of all the useful features (no
| distinct enum variant types, traits only half-exist) because
| you have to code to the second, hidden language that it
| actually compiles to.
| whytevuhuni wrote:
| Yeah, I've hit the enum variant type issue myself (e.g. a
| function guaranteed to return one of the variants), but in
| practice it hasn't been that big of an issue. There's also
| _std::mem::discriminant_ [1] which helps a bit.
|
| What do you mean by traits only half-existing?
|
| [1] https://doc.rust-
| lang.org/stable/std/mem/fn.discriminant.htm...
| mrsofty wrote:
| Godbolt is a legend and I don't care what the others say. I
| believe him when he said he was "just helping" that sheep through
| the hedge. Well deserved praise Matty-poo and well written
| article.
___________________________________________________________________
(page generated 2025-05-07 23:01 UTC)