Hacker News new | ask | show | jobs
by thatxliner 874 days ago
> Rust's addition of operator overloading is also a perplexingly bad decision. I don't think there is a single worse design decision in C++ or Rust than allowing someone to redefine what + means on some arbitrary type.

Why is that? Won’t it be more ergonomic than needing to call, say `.add`, when you want to do something clearly similar to addition. It’s just sugar, no?

In Java, there is no operator overloading, so if you want to compare 2 strings by contents rather than their memory addresses, you have to use a `.equals` method. Comparing 2 strings by their contents is the most common use case, so it not being the default is a design flaw (similar to how you need to `break` out of a `case` in most languages).

If you can’t overload the addition operator, then why have a whole language construct that can only be used on primitive integers and floats?

3 comments

> Why is that? Won’t it be more ergonomic than needing to call, say `.add`, when you want to do something clearly similar to addition. It’s just sugar, no?

Especially since, in a typed language, this function would be desugared at compile time and (probably) aggressively inlined, making it hardly different from the compiler builtins for adding floats and ints. Unless, of course, you're doing something like allocating to concatenate strings.

Really, though, it's more of a developer common-sense issue than a language one.

When writing systems software like a kernel, clarity is far more important than ergonomics. The basic operators aren't functions, they're translated directly into opcodes based on the types of the operands. Operator overloading allows them to be either opcodes or functions, and if you think about the amount of work that goes into a function - saving registers, creating a stack frame, pushing addresses, multiple branches - versus a single opcode, as a kernel developer you really don't want this to be hidden behavior based on whether something has been overloaded or not. In the best case the compiler will optimistically inline multiple instructions, in the worse case it will call a function before you've even set up a stack.
SIMD intrinsics are also translated directly into opcodes. There's very little reason to have floating point ops represented by special syntax rather than ordinary functions like fadd() fsub() fmul() fdiv() fmuladd(), other than mere legacy. (And they might not even be simple opcodes in a soft-float implementation.) I mention floating point specifically because that's where even something basic like the order of operations can affect the outcome, so the extra precision actually matters.
What if your target cpu doesn't have, say, floating point operations, or integer division. Will Zig refuse to compile any code that uses these operations? Will it inline the emulation code? Or gasp generate a call out to a library function?
This is an extremely common issue to deal with when creating a kernel, which is, again, why levels of indirection in code and hiding context only makes life more difficult for a systems developer. The kernel may need to enable accelerators, switch between arm and thumb, enable an fpu, clear cache lines, etc. A lot of decisions will be made by the compiler, but based upon parameters passed in at build time, and a lot of it will be arch specific code interwoven in assembly. And there are tons of times when a compiler will generate things you don't want, forcing you to add pragmas and so forth.
Thing is, Rust has full support for hygienic macros so operator overloading could've been added as part of that. You'd just have to write, e.g. int_expr![a + b] or whatever, but that would've made the syntax fully extensible.
Macros are easy to spot, the whole point of operator overloading is that it's a trojan horse. It might do simple addition, it might do a heap allocation and talk to a printer.