News for hackers. Stuff that matters.
Re:C++ vs. Rust Build Times by hansonkd on Friday January 06, 2023 @06:10AM (#34273438)
Can you give an example for #2? I picked up Rust this year and never found that to be the case. In fact Rusts type system is extremely clear to me and was pretty straight forward coming from a Python world. I don't think I ever encountered anybody quoting Category theory at me.

I used Haskell for a few years and the main problem with it is that to do anything you need to use Monads, Functors and a lot of complex types. It's a requirement to even write the most basic program. "strange" functional stuff proliferates haskell like `foldl` and `foldr` and a extremely discouraging Lazy computation model that makes it almost impossible to know what the compiler will do unless you have a near perfect comprehension of all the language.

Rust to me is as simple as writing annotated Python or Typescript, with the only addition that a beginner needs to know is that there is a new option that those two languages don't have which is pass by reference or pass by value. The lifetime stuff was really easy to get, "pass a value into a function, can't use it anymore."

The Rust compiler gives extraordinarily useful error messages. I don't know if haskell improved but you can end up with some very strange stuff to figure out.

The only real hiccup I had when learning Rust was that going from `sync` to `async` code had a bit of a learning curve. It was simple for basic stuff, but the problem were that Future's have their own Lifetime that depends on every piece of data used in the computation between `await` calls. This means all your data needs to be Sync + Send if you want to use Async.

Re:C++ vs. Rust Build Times by petesergeant on Friday January 06, 2023 @06:55AM (#34273738)
> the only addition that a beginner needs to know is that there is a new option that those two languages don't have which is pass by reference or pass by value

I think anyone who's done anything beyond beginner JavaScript is going to understand the difference between pass by value and pass by reference, even if they don't call them that?

    function foo1( val : string ) { val = "hi" }
    function foo2( val : [string] ) { val[0] = "hi" }

    const bar : [string] = ["yo"];
    foo1( bar[0] );
    console.log( bar );
    foo2( bar );
    console.log( bar );
Re:C++ vs. Rust Build Times by iterateoften on Friday January 06, 2023 @07:30AM (#34273945)
The big difference when I use Rust is that in Rust every variable you accept in a function requires you to make that choice. Whereas JS it’s kind of chosen for you. In your example you had to go out of your way to do it. So just a bit more mental overhead for implementing libraries. But not a huge deal.
Re:C++ vs. Rust Build Times by ptero on Friday January 06, 2023 @07:55AM (#34274123)
It is not just the abstract knowledge of the difference, but the fact that "let b = a" makes a undefined and, especially, some comfort with a Rust-ic way of dealing with this. My 2c.
Re:C++ vs. Rust Build Times by gavinray on Friday January 06, 2023 @03:40PM (#34280731)

  > I think anyone who's done anything beyond beginner JavaScript is going to understand the difference between pass by value and pass by reference, even if they don't call them that?
Pass-by-reference is a concept that is unfamiliar to many (most?) modern JS/TS developers, as the common idiom is that you "return" a new object or value at the end of your function.

If you want to update a value in an existing object, you pass it by value and return a clone of it with the updated values

Performance doesn't matter, you can do the most pants-on-head-stupid thing you can imagine, or have your frontend page/component be re-rendering 20 times on every state change, and it makes no perceptible difference. (This has been my experience)

It wasn't until I learned C that I realized you might even program in this fashion (somewhat to my horror) where passing variables to functions could mutate them

Re:C++ vs. Rust Build Times by petesergeant on Sunday January 08, 2023 @12:05AM (#34296215)
> It wasn't until I learned C that I realized you might even program in this fashion (somewhat to my horror) where passing variables to functions could mutate them

If you pass an array to a function in JavaScript, then any changes you make to it will mutate the version the caller has.

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...