Remix.run Logo
▲ stusmall a day ago

You can. Something like toggle is nice because it is very clear at a glance. That's a subjective style call.

But under the hood it's all the same https://doc.rust-lang.org/stable/src/core/bool.rs.html#157-1...

▲brabel 19 hours ago | parent | next [-]

My first thought was that this is useful when you need a lambda that does this, now you can use ::toggle

▲SkiFire13 14 hours ago | parent | next [-]

You could do `bool::not` before, but this is clearer and doesn't require imports.

▲CUViper 8 hours ago | parent [-]

`bool::not` returns the value, while `bool::toggle` is more like a `not_assign`.

▲masklinn 16 hours ago | parent | prev [-]

Now I just need the integer types to gain in-place inc() and dec()

I don’t know how to feel about toggle returning neither old nor new value tho.

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

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 21 hours 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 15 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 13 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 21 hours ago | parent [-]

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

▲aw1621107 20 hours 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 20 hours 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 18 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 17 hours ago | parent | next [-]

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

▲pjmlp 18 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 7 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.

▲torginus 13 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.

▲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.

▲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.

▲ 12 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.

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

Is x = !x faster than x.toggle() because from my naked eyes no function is called? Or does the compiler know that the function toggle should be replace with `!=`? If the compiler does know that, how is that knowledge implemented? Is there a table of special functions that should be replaced with something else? Or some more clever way?

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

Thanks to Matt Godbolt, we can ask Compiler Explorer!

https://rust.godbolt.org/z/dPn3Y4r59

The optimizer gives the exact same machine code for both functions, and so it just outputs a single function and gives it an alias.

The compiler has a technique called "inlining" https://en.wikipedia.org/wiki/Inline_expansion

The LLVM backend used by Rust is jokingly said to have a inlining heuristic of "yes" ie it will always try to inline everything because that's usually faster.

▲xiej a day ago | parent [-]

Unfortunately it's not inlined in debug/no optimization mode so that's rough.

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

I'll go out on a limb and hypothesize that there are going to be much larger difference between debug and release mode for any non-trivial code that the overhead of `toggle` in debug mode will quite literally never end up mattering to anyone in practice.

▲lanyard-textile 18 hours ago | parent | prev [-]

Why should it be? That's a feature of debug.

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

It has the #[inline] attribute.

https://std-dev-guide.rust-lang.org/policy/inline.html

▲kibwen 13 hours ago | parent [-]

In this case the inline attribute probably isn't strictly necessary, because rustc heuristically inlines any functions that it deems trivial enough.

▲ 15 hours ago | parent | prev [-]
[deleted]
▲ 14 hours ago | parent [-]
[deleted]