News for hackers. Stuff that matters.
Re:C++ vs. Rust Build Times by nyanpasu64 on Friday January 06, 2023 @08:23AM (#34274300)
Some Rust programmers, when faced with performing operations on a sum type (like Result) or container, tend to reach for point-free map-filter-reduce code stuffed with higher-order functions, lambdas, trait generics, and return type inference, requiring looking up the documentation or implementation of multiple helper microfunctions, and asking the compiler for the inferred return type. My preference is usually to write imperative code matching over objects or looping over iterators, except in cases like "minimum of list" where the loss of clarity of a loop exceeds the loss of clarity of a reduce() method call.

Separately, to express code which creates multiple non-const references into a single object (trivial in any other imperative or OOP language), you need compromises like verbose Cell, overhead RefCell, ugly and footgun-filled UnsafeCell and raw pointers, unsafe confusing (and currently unsound, see https://github.com/rust-lang/rust/issues/63818) Pin, or experimental ghost cells (https://plv.mpi-sws.org/rustbelt/ghostcell/paper.pdf, not sure the current implementations).

Re:C++ vs. Rust Build Times by hansonkd on Friday January 06, 2023 @09:16AM (#34274765)
YMMV but I personally have never faced either problem having written tens of thousands of lines of Rust at this point. But to be honest I never worked on a large team of Rust engineers.

One of the benefits of Rust is that it gives you an option to switch to imperative style if thats what you choose which isn't the case for Haskell or purely functional languages.

Tacit programming was never a problem as the IDE does a pretty phenomenal job of telling you the types anyway.

non-const references in a single object can simply be solved by making the whole object mutable, which is essentially what Python et al. do for you anyways.

I never had to use any unsafe code or pointers. Not sure if that way of thinking is carried over from C and C++ engineers. The only time I think I use Cell or the like is if I have a global static variable that I need to fill in at runtime. But that is more of a code-smell for most or at best a more advanced programming design that beginners don't need to worry about.

The best way to solve the last problem is to understand your data model from the start and make what objects need to be mutable mutable.

Re:C++ vs. Rust Build Times by jononor on Friday January 06, 2023 @10:27AM (#34275702)
To be fair, this kind of thing even happens in JavaScript. Some use functional programming with immutable datastructures, higher order functions, super generic code, using some niche FP library with its own idiosyncrasies - when simple and imperative would do just great.
Re:C++ vs. Rust Build Times by cogman10 on Friday January 06, 2023 @10:27AM (#34275707)
> Some Rust programmers, when faced with performing operations on a sum type (like Result) or container, tend to reach for point-free map-filter-reduce code stuffed with higher-order functions, lambdas, trait generics, and return type inference, requiring looking up the documentation or implementation of multiple helper microfunctions, and asking the compiler for the inferred return type. My preference is usually to write imperative code matching over objects or looping over iterators, except in cases like "minimum of list" where the loss of clarity of a loop exceeds the loss of clarity of a reduce() method call.

More and more, I find this is just a general problem and not a rust specific one. Any language with enough syntax flexibility will have a group of programmers that try to treat the language like lisp or haskell. (I recently ran into exactly this with a typescript lib I've been working with).

C++ is no stranger to this problem, it's famously the reason Java doesn't have operator overloads.

What I'm also finding is that complex style of programming is annoying to find in the company code base, but is rarely one that catches on in the ecosystem as a whole.

Re:C++ vs. Rust Build Times by epage on Friday January 06, 2023 @11:37AM (#34276906)
> Some Rust programmers, when faced with performing operations on a sum type (like Result) or container, tend to reach for point-free map-filter-reduce code stuffed with higher-order functions, lambdas, trait generics, and return type inference, requiring looking up the documentation or implementation of multiple helper microfunctions, and asking the compiler for the inferred return type. My preference is usually to write imperative code matching over objects or looping over iterators, except in cases like "minimum of list" where the loss of clarity of a loop exceeds the loss of clarity of a reduce() method call.

I know for myself, I overdid it at times as I was learning the trade offs.

Where I've currently settled is simple map/filter operations to tweak things into a more readable state are better than the more procedural approach. Also keep to simpler combinators (e.g. no `map_or` / `map_or_else`). If things become too complicated, be procedural. If there are side effects, be procedural.

Another way I have for framing it is the code layout (files, functions, blank lines, newlines, indentation) should be used to highlight the business logic. Early returns, `?`, post-fix combinators, etc should be used to reduce boilerplate that detracts from the business logic.

Re:C++ vs. Rust Build Times by nextaccountic on Friday January 06, 2023 @06:10PM (#34282701)
> If there are side effects, be procedural.

Yep! That's why Iterator::for_each https://doc.rust-lang.org/stable/std/iter/trait.Iterator.htm... is more often than not a code smell [0]; it exists mostly for the sake of writing side effects in functional style.

So something like

    something.map(|x| ..).for_each(|x| do_something(x));
Should probably be written

    let iter = something.map(|x| ..);

    for x in iter {
        do_something(x);
    }
It's still okay to use Iterator::map and other iterator combinators, but using for instead of for_each here is probably easier to read.

[0] One exception is in the documentation "In some cases for_each may also be faster than a loop, because it will use internal iteration on adapters like Chain."

Re:C++ vs. Rust Build Times by nextaccountic on Friday January 06, 2023 @12:07PM (#34277444)
> Some Rust programmers, when faced with performing operations on a sum type (like Result) or container, tend to reach for point-free map-filter-reduce code stuffed with higher-order functions, lambdas, trait generics, and return type inference, requiring looking up the documentation or implementation of multiple helper microfunctions, and asking the compiler for the inferred return type

Can you give an example of that? Do you mean something like, using .map() and .map_err() on Result?

I can't imagine myself not using those methods; they make programming much more enjoyable, and should be considered elementary as of 2023.

(Note: about .map_err() specifically, error conversion is kind of important in Rust; it's usually performed automatically by the question mark operator but sometimes you need to do it explicitly, and doing so without .map_err() usually obscures the intent of the program for no gain in simplicity.)

Re:C++ vs. Rust Build Times by AnIrishDuck on Friday January 06, 2023 @01:45PM (#34279023)
> Separately, to express code which creates multiple non-const references into a single object (trivial in any other imperative or OOP language),

So multiple mutable references to the same data? I'm personally happy that's harder to do in Rust, as it enables anti-patterns like God Objects [1] and spooky action at a distance [2].

I don't like the "you're doing it wrong" response when people point out flaws, and that's not what I'm trying to say here. You can write perfectly fine code with remote mutation. This is just one example where the things that Rust chooses to make harder are also the things I have a harder time understanding.

I'll also agree that remote mutation is still necessary for some important use cases (e.g. testing, memoization). Then you have the `Cell` / `RefCell` escape hatches. Rust is pragmatic about such things.

> you need compromises like verbose Cell, overhead RefCell, ugly and footgun-filled UnsafeCell ...

The actual need for these constructs tends to be rare in my experience.

We have tens of thousands of lines of Rust in our codebase and ~five instances of these wrappers. They're really not hard to avoid. And actually, looking through the results here, I've found a few cases we actually _shouldn't_ be using `RefCell`.

> unsafe confusing (and currently unsound ...) Pin

This is a fair point. `Pin` always felt to me like a compromise to get async out the door. The best thing I can say is it's proof that the Rust team is willing to not let the perfect stand in the way of the useful.

But I'll concede it still is difficult to understand and complicates the async picture. This is definitely an example where Rust's underlying philosophy makes things that should be easy harder.

1. https://en.wikipedia.org/wiki/God_object 2. https://en.wikipedia.org/wiki/Action_at_a_distance_(computer...