Here's a struct that maintains an invariant - say, that field a is less than field b. That invariant should be set when it is created.
You find a bug where a is greater than b. Where is the bug? With a struct, it can be anywhere in the code - any line that touches the struct. But with a (well designed) class, a and b are private, and so you only have to look at lines of code within the class. The surface area where the bug can be is much smaller.
The bigger the code base and the more well-used the class is, the more this matters.
About the Rust code itself; the primary issue was code duplication rather than preventing the use of Rust altogether. Since the suggested code is not merged, every driver needs to write the same code over and over again. That was also something that the maintainer suggested himself (?). There is of course a big downside, but I am not sure if this justifies all the hate.
That doesn't sound like he's only talking about in his area to me
https://rust.godbolt.org/z/r9rP6xohb
Rust realizes the vector is never used, and so never does any allocation, or recursion, it just turns into a loop to count up to 999_999_999.
And some back of the napkin math says there's no way either benchmark is actually allocating anything. Even if malloc took 1 nanosecond (it _doesn't_), 999_999_999 nanoseconds is 0.999999999 seconds.
It _is_ somewhat surprising that rust doesn't realize the loop can be completely optimized away, like it does without the unused Vec, but this benchmark still isn't showing what you're trying to show.
> There's no implicit string copying in Rust. Even when passing String ownership, it will usually be passed in registers.
The String metadata can be passed in registers if LLVM does that optimization, but it's not guaranteed and doesn't always happen. Rust move is just a memcpy, there are situations where LLVM doesn't optimize them away, resulting in Rust programs doing a lot more memcpy than people realize.
> It's idiomatic to use `&str` by default.
True if you want it to be immutable, but this actually adds to my point. That is the default behavior in Mojo without having to understand things like deref coercion and the difference between `&str` and `&String`. In Rust it's an unintuitive best practice, which everyone has to learn pretty early in their journey. In Mojo they get the best behavior by default, which gives them a more gentle learning curve, important for our Python audience. Default behavior > idiomatic things to learn.
> The borrow checker doesn't influence destructors.
I didn't claim that, my point was that Rust does do runtime checks using drop flags, to check if a value should be dropped. This can be done statically during compilation, but won't happen if the initialization state of an object is unknown at compile time: https://doc.rust-lang.org/nomicon/drop-flags.html
> &String and usize can't have a destructor, and can be forgotten at any time
In the example, the call stack is growing with a new reference and usize in each frame for each call. This is why tail recursion in Rust has so many issues, those values need to be available to the end of scope to satisfy Rust's guarantees, they can't be "forgotten at any time". It also overflows the stack a lot faster.
> Their benchmark intended to demonstrate tail call cost compiles to a constant, with no `factorial()` calls at run time. They're only benchmarking calls to black_box(1307674368000). Criterion put read_volatile in there. Mojo probably uses a zero-cost LLVM intrinsic instead.
If the Rust benchmark isn't calling `factorial()` it should be instant and faster than Mojo, the Rust version is must slower. `benchmark.keep` in Mojo is a "clobber" directive, indicating that the value could be read or written to at any time, so LLVM doesn't optimize away the function calls to get the result.
Thanks for taking the time to read the post, and write out your thoughts. Really enjoying the discussion around these topics.
And no: in the example with `&String` and `usize`, the stack isn't growing: https://rust.godbolt.org/z/6zW6WfGE7
Here's my OCaml library influenced heavily by Erlang: https://bitstring.software/examples/
For example:
set -e
var="$( false )"
if [ $? -eq 0 ] ; then
echo Ok: "$var"
else
echo Not ok: $?
fi
If you run this program, neither "Ok" or "Not ok" will be echoed, because the program will exit with an error on the `var=` line. (Not to mention the $? on the not-ok line won't work because it will be the exit code of the `[` test command in the conditional, not the exit code of the captured subshell command).Instead the following will work:
set -e
if var="$( false )" ; then
echo Ok: "$var"
else
echo Not ok: $?
fi
Note that this will _not_ work: if ! var="$( false )"; then
echo Not ok: $?
fi
Your output will be "Not ok: 0". This is because negation impacts the exit code of the previous command.rc=0 long command || rc = $? if (( rc == 0 ))...