This is what I’ve found:

In C and C++, two different operators are used for calling methods: you use . if you’re calling a method on the object directly and -> if you’re calling the method on a pointer to the object and need to dereference the pointer first. In other words, if object is a pointer, object->something() is similar to (*object).something().

What about Rust?

Rust doesn’t have an equivalent to the -> operator. Instead, Rust has what is called automatic referencing and dereferencing.

In other words, the following are the same:

p1.distance(&p2); (&p1).distance(&p2); Note: this automatic referencing behavior works because methods have a clear receiver-the type of self. Given the receiver and name of a method, Rust can figure out definitively whether the method is reading (&self), mutating (&mut self), or consuming (self).

I am not sureI understood the note correctly, what does it mean that there is a clear receiver? Also, doesn’t Rust actually have an operator for dereferencing (*) as well?

  • nous@programming.dev
    link
    fedilink
    English
    arrow-up
    8
    ·
    2 days ago

    By clear receiver it means there is only one function a name can point to. For instance you cannot have:

    struct Foo;
    impl Foo {
        pub fn foo(self) {}
        pub fn foo(&self) {}
    }
    
    error[E0592]: duplicate definitions with name `foo`
     --> src/lib.rs:5:5
      |
    4 |     pub fn foo(self) {}
      |     ---------------- other definition for `foo`
    5 |     pub fn foo(&self) {}
      |     ^^^^^^^^^^^^^^^^^ duplicate definitions for `foo`
    

    Which means it is easy to see what the function requires when you call it. It will either have self or &self and when it is &self it can auto reference the value for you. This is unlike C and C++ where you can overload a function definition with multiple different signatures and bodies and thus the signature is important to know to know which function to actually call.


    Yes rust has a operator for dereferencing as well (the *). This can be used to copy a value out of a reference for simple types the implement Copy at least.

    • Ephera@lemmy.ml
      link
      fedilink
      English
      arrow-up
      4
      ·
      2 days ago

      And if you’re wondering, what the hell, if there’s no overloading, how do you even design an API???
      You’ll learn about the From and Into traits, which allow you to take multiple types for a parameter, because those traits implement the conversion between those types.

      • beeb@lemm.ee
        link
        fedilink
        arrow-up
        4
        ·
        edit-2
        2 days ago

        If you want polymorphism which looks more like what you’re describing, you can put trait bounds on parameters instead of a type and it will accept any parameter that implement those traits. E.g. If you want to accept anything that can be turned into an owned string with “.into()” you type an argument with “impl Into<String>”. Another common one is “impl AsRef<Path>” to accept a path, path reference, PathBuf etc.

    • TehPers@beehaw.org
      link
      fedilink
      English
      arrow-up
      2
      arrow-down
      1
      ·
      2 days ago

      Everything you said makes sense, but just want to add to your note at the bottom: * can also be used for reborrowing (&amp;*blah, &amp;mut *blah, &amp;***blah, etc). This is useful for getting a shared borrow (&amp;T) or unique/exclusive borrow (&amp;mut T) from another type of pointer (like Box, Arc, or even MutexGuard).

  • orclev@lemmy.world
    link
    fedilink
    arrow-up
    6
    ·
    2 days ago

    It’s because Rust is a significantly higher level language than C (and C++ gets held back by it’s C roots). In C there’s no such thing as a method, there’s only pointers. The closest you get to a method is a function pointer. C++ expanded on that with the concepts of class, object, and method, but stopped half way leaving an object and a pointer to an object as distinctly different concepts that are treated differently by the language. In order to do anything with a pointer besides change its address in C++ it must be dereferenced. The -> operator in C++ is really syntactic sugar for a dereference followed by a method invocation. In other words these are equivalent foo->bar() and (*foo).bar() as stated in the quote you provided. C and C++ treat pointers as their fundamental abstraction and treat everything as special cases of pointers.

    Rust in contrast treats Objects as first class abstractions. It doesn’t really matter whether you’re dealing with the owned object or a reference to that object owned by a different scope, in either case the compiler knows at compile time what its type is and what traits it implements. Given that knowledge whether the method is invoked from the owned instance or a reference it’s treated exactly the same. Further because pointers as opposed to references are treated as second class citizens (relegated to the “here be dragons” of unsafe), the concerns of C/C++ about distinguishing between a pointer assignment and a pointer dereference aren’t particularly important. In general the times you need to explicitly dereference something are far more uncommon in Rust, with the compiler generally being able to work out on its own when it needs to dereference something or not.

    The bit about the receiver just means that the compiler at compile time knows the actual concrete type of all references with the exception of dyn references which force compile time dynamic dispatch (in which case the compiler inserts and utilizes a vtable exactly like C++ does).