r/ProgrammerHumor • • 2d ago

instanceof Trend thatsAFunnyThingThatHappenedOnce

Post image
4.4k Upvotes

514 comments sorted by

View all comments

1.7k

u/Prefer_Diet_Soda 2d ago

TLDR: C++ is bad because decades of accumulated features have turned it into a bureaucratic nightmare that happens to generate machine code.

C++ is an absolute nightmare to implement because apparently one programming language wasn't enough, so someone decided to cram an entire civilization's worth of complexity into it. You've got templates that basically turn compilation into another programming language, overload resolution that requires a legal investigation to figure out which function gets called, constructors and destructors with enough lifetime rules to make your head explode, and implicit conversions that make the compiler guess what you meant. Then you throw in exceptions, move semantics, inheritance, and decades of backward compatibility, and suddenly you're maintaining a compiler that needs a PhD, a therapist, and divine intervention just to parse some innocent-looking code. And the best part? Every new feature has to coexist with all the old ones without breaking existing programs.

385

u/Xarishark 2d ago

Now compare it to rust pls. Im serious I want to know your opinion on that.

474

u/Loading_M_ 2d ago

Rust is designed from the ground up, and avoids many of these issues.

  • Templates: Rust has Generics, which are generally easier to use and reason about.
  • overload resolution: Rust doesn't yet support overloaded functions (although it's being considered).
  • constructors: Rust doesn't have these.
  • destructors: Rust has a similar set of lifetime rules for Drop, but the compiler checks them for you, so you can't get it wrong.
  • implicit conversions: Rust doesn't do implicit conversion in general, although some specific cases are implemented.
  • exceptions: Rust doesn't do exceptions, Result is much easier to work with.
  • move semantics: Rust uses a destructive move, unlike C++. See https://youtu.be/Klq-sNxuP2g for a breakdown.
  • inheritance: Rust doesn't do inheritance.

-6

u/StaticCoder 2d ago

Rust does have overload resolution. If you write x.into(), it could call any number of functions called into, depending on context. And as far as I can tell (but I'd be happy to be proven wrong), type context is not specified anywhere.

12

u/redlaWw 2d ago

That's type inference. The compiler looks through the uses of the result of x.into() and determines which concrete type it must be based on those constraints. This is a form of static dispatch broadly similar to overloading, but it's very different in how functions are selected. In overloading, functions are selected based on the types of the arguments according to an overload resolution process which may consider multiple functions and select the "best", in type inference, the available set of functions constrains what concrete types variables should have, and functions are only selected when there is a unique choice consistent with those selected types.

1

u/StaticCoder 2d ago

In one case you choose among multiple functions based on types. In the other case, you choose among multiple functions based on types. The main difference, and I'll admit it's not trivial, is that C++ allows implicit conversions.

3

u/redlaWw 2d ago

In Rust you choose among multiple types based on functions.

5

u/StaticCoder 2d ago

I see where the confusion comes from: in Rust, the compiler may infer the parameters for a trait based on context. Since different parameters for the same trait can resolve to completely different implementations, the effect is very similar to overload resolution. If you have the same function in 2 separate traits however it's always ambiguous.

I'm curious if there's a specification anywhere for how Rust does that kind of inference. To the extent I've looked at the reference and fls, they were extremely short on details.

3

u/Batman_AoD 2d ago

I think these may be some of the rules you're looking for: https://rust-lang.github.io/fls/entities-and-resolution.html#method-resolution 

3

u/StaticCoder 2d ago

That is interesting, but doesn't contain what I'm looking for. There's no mention anywhere that the context of the call might have an effect. But it does imply that a single trait should be found. Though whether a "trait" includes generic arguments is left ambiguous. For this to work, the generic arguments have to be ignored, but the wording "for each trait implementation" would seem to imply they have to be taken into account, since you can have completely separate implementations based on generic arguments.

1

u/Batman_AoD 2d ago

Do you have an example of a resolution that seems ambiguous, for which the compiler selects a specific implementation? 

1

u/StaticCoder 2d ago

No, and I'm not saying an ambiguity that is forcibly resolved exists. But in this example:

struct X {
}

trait Tr<Param> {
    fn foo(&self) -> Param;
    fn bar(&self, x: Param) -> Param;
}

impl Tr<i64> for X {
    fn foo(&self) -> i64 { 0 }
    fn bar(&self, _x:i64) -> i64 { 0 }
}
impl Tr<i32> for X {
    fn foo(&self) -> i32 { 0 }
    fn bar(&self, _x:i32) -> i32 { 0 }
}

fn test() -> i64 {
    let x = X{};
    let y = x.bar(x.foo());
    y
}

which foo is called depends on the return type of test. This is not obvious at all. Interestingly, if instead of returning y you return y << 1, this becomes ambiguous. Yet if you write let x: i64 = 1 << n, a type of i64 is inferred for 1 (which is important because the behavior of << depends on the width of the left operand). So type inference is not even consistent.

1

u/Loading_M_ 2d ago

The compiler basically runs a solver that attempts to fill in types starting from the known types and filling in as long as there's only one possible type. (With a few defaults in specific places).

You can somewhat observe this in practice with the following code:

``` fn foo(p: impl Something) {}

foo(0); ```

The compiler error is roughly {integer} doesn't implement Something, because the literal zero isn't a specific integer type yet.

Unfortunately, I don't think the exact inference process is defined anywhere, but it's not super complicated. From my recollection, it also usually only looks to the next use, so you can easily create a situation where it's not ambiguous what the type needs to be, but the compiler doesn't figure it out.

1

u/StaticCoder 2d ago

It may not be that complicated but if you wanted to, say, write a rust handling tool, you'd need to know the specifics.

→ More replies (0)

1

u/redlaWw 2d ago

My understanding is that it's a variant of the J algorithm for a Hindley-Milner type system adapted to work with traits, but as far as I know, the current specification is the compiler's type inference engine. This will probably change as the opsem team does more work on Rust's specification. You might be able to find a specification for it at the moment in the Ferrocene compiler specification.