Remix.run Logo
▲ rauli_ a day ago

For someone who has decades of experience from other programming languages this does not look clear to me at all but rather confusing. Calling an method can change value of variable of primitive type?

▲ninkendo a day ago | parent | next [-]

There’s nothing special about primitives, they’re just types like any other. A method taking `&mut self` can always modify self, on any type. But you can only call such methods if the value is mutable to begin with, so it’s not really that interesting.

It’s only weird if you come from languages like Java where “primitives” are distinct from “objects”, which is a self-imposed limitation the language imposes.

▲Groxx a day ago | parent [-]

Or Go (also treats primitives rather specially) or roughly any language without extension methods (because primitives are at a minimum a pre-defined type). Which is quite a lot of languages really.

Otherwise agreed, methods like this can be mutable in any language that doesn't heavily specialize primitives.

▲pjmlp a day ago | parent [-]

Eiffel or Smalltalk are two languages, among others I could chose from, where primitives and others types are differently implemented, yet unless you dive into the nitty gritty details, they look exactly the same to the developer as the compiler handles them exactly the same, and there are no extension methods to be seen.

▲kragen 18 hours ago | parent [-]

However, it would be impossible to implement a #toggle method in Smalltalk that behaves like this, because you're calling it on the singleton True (or False) object; Smalltalk local variables, like Lisp variables, are references to objects rather than objects themselves.

(You obviously know this, but it might be a surprise to many people reading this thread.)

▲pjmlp 16 hours ago | parent [-]

Yes, but that is orthogonal how primitives and reference types get exposed to the developer.

Something like toggle is also impossible in languages that favour immutability and forward progress requires creating new variables, like several functional and logic languages.

▲rtpg a day ago | parent | prev | next [-]

C++ lets you do this kind of stuff as well.

Primitive types and composite types having the same capabilities is a bit of a value statement for C++ and Rust IMO, and it's missing in a lot of languages downstream (IMO) of the "every variable is a reference to a slot/pointer" philosophy taken by most every managed language

▲tialaramex a day ago | parent | next [-]

C++ doesn't have methods/ "member functions" on the primitive types.

One reason is that C++ doesn't know where to put them, for user defined class, struct or union types these "member functions" as their name suggests live inside the class/ struct/ union definition but for a primitive where would these live?

In Rust they're just impl blocks, for example the method we're discussing here is:

    impl bool {    
        pub const fn toggle(&mut self) {
            *self = !*self;
        }
    }
▲pjmlp a day ago | parent [-]

Only because Unified Call Syntax is yet to land into the language, after several failed attempts.

▲aw1621107 a day ago | parent [-]

And I think there's a decent chance that it never will, at least in the form it has been proposed in in the past (i.e., member function syntax being effectively turned into free function calls). P3027 "UFCS is a breaking change, of the absolutely worst kind" [0] argues quite strongly that that particular form of UFCS is basically completely broken.

[0]: https://isocpp.org/files/papers/P3027R0.html

▲pjmlp a day ago | parent [-]

As WG21 goes, it is a matter of advocacy, persistency, and getting the required votes in the room, at least if the process doesn't change even with how C++26 contracts turned out to be.

▲aw1621107 21 hours ago | parent [-]

I mean, sure, but that's basically a tautology since that's how everything made it into the standard into the first place. Those are necessary but not sufficient conditions.

▲tialaramex 20 hours ago | parent | next [-]

I'm with you on this and would point at std::hive as an example of the results.

▲pjmlp 21 hours ago | parent | prev [-]

Not really, in first place it was basically CFront 2.0, alongside C++ARM akin to the K&R C book, with last minute addition of HP STL implementation.

The only piece that wasn't really available and was made in a waterfall design approach was export templates, with the known outcome that no one other than EDG has even bothered implementing it.

Post C++98 is when we got into the "advocacy, persistency, and votes" strategy.

▲aw1621107 11 hours ago | parent [-]

You still needed "advocacy, persistency, and votes" to get stuff into C++98. It's just that some things need a lot more of the former to get the latter than others.

▲ a day ago | parent | prev [-]
[deleted]
▲stusmall a day ago | parent | prev | next [-]

The big clue that'll jump out to any rust dev is the type signature:

pub const fn toggle(&mut self)

The "&mut" says that toggle takes a mutable reference to the instance it is called off of. This is also called a mutable borrow. You might think that would get buried in the program because you don't always check the type signature of every method you call, but there are a few reasons that muts really jump out in rust. The languages immutable by default nature is one and the other is that several IDE have features to specifically draw attention to mutable instances/references. It's a fairly common pattern in rust to have helper methods on a type that take a mutable reference or ownership of self.

TL;DR: Rust's ownership model is very unique. Once it clicks its the cat's pajamas, but experience in other languages don't map well to this part of the language.

▲zdimension a day ago | parent | prev | next [-]

When you call a method on something in Rust, the method gets passed that something (as a parameter called self). But the method gets to choose whether it's by move/copy or by reference. Here, toggle gets the bool passed by mutable reference.

In effect, if x is a bool, doing x.toggle() is rewritten by the compiler as bool::toggle(&mut x), which can of course modify x.

▲saghm a day ago | parent | prev | next [-]

Not quite; the method uses `&mut self`, so it actually operates on a mutable reference to the value that happens to be a primitive. If you declare a boolean without specifying the binding as mutable, you'd get a compiler error from trying to call this method. Moreover, if anything else had a reference to it at the same time, it would also cause a compiler error to call this method.

This is definitely something that Rust does a bit different than most other languages, but it's not anything specific to this method, booleans, or particularly new to the language. Most people will learn about the common different ways of defining a method (via ownership, shared reference, or mutable reference) fairly early on in programming Rust.

▲scott_w a day ago | parent | prev | next [-]

Why wouldn’t it be possible? The distinction in modern languages between primitive and not is an arbitrary one when it comes to stuff like this.

▲torginus 16 hours ago | parent | prev | next [-]

I think a major issue in all OOP languages is determining which operation mutates and which does not, and often having both operations, or even worse, if there's a third, static method. This is redundant and creates opportunity for code style divergence.

An example would be having both `Vector3.Add` and `Vector3.AddToSelf` instance methods as well as third `Vector3::Add` which takes two Vector3s.

▲crabmusket a day ago | parent | prev | next [-]

I guess you're experiencing the feeling a mathematician gets when seeing a line like

x = x + 1

▲jason_oster a day ago | parent | prev | next [-]

Think of having the method for AtomicBool, where you are forced to call methods for all value accesses. `atomic_bool.toggle(Ordering::SeqCst)` would read much better than `atomic_bool.fetch_xor(true, Ordering::SeqCst)`.

▲nicoburns a day ago | parent | prev | next [-]

What may be surprising to you coming from other languages, is that primitive types in Rust have their own methods! So methods applying to primitives is not unusual at all in Rust.

▲ 15 hours ago | parent | prev | next [-]
[deleted]
▲throwaway2037 a day ago | parent | prev | next [-]

    > decades of experience from other programming languages
What languages?
▲adastra22 a day ago | parent [-]

Most (not all) static & ahead-of-time compiled languages. C++ and Java come to mind. Method calls on built-in primitive types is more of a dynamic language thing (think: ruby, python) that got extended into compiled languages in recent memory.

▲worik a day ago | parent | prev | next [-]

> Calling an method can change value of variable of primitive type?

So long as the variable is mutable it's value can be changed

▲kibwen a day ago | parent | prev [-]

In addition to what the sibling commenters have written, in order for this to compile, the variable would need to be declared as mutable.