Why is `sin` a method?

In almost every other programming language, if I want to take the sin of a floating point number, I do sin(x), but in rust, we do x.sin(). While it's no big deal to write the function fn sin(x: f64) -> f64 { return x.sin(); }, I was curious as to why it's done an object-oriented way in the first place, as the answer may give me important insights about the language -- especially as it pertains to engineering.

2 Likes

Rust doesn't support overloading functions, e.g. sin(f32) and sin(f64). Methods work around this.

12 Likes

FYI

fn sin<F: ::num::Float>(float: F) -> F {
    float.sin()
}
6 Likes

In Rust, all method calls are purely syntax sugars. Whenever one would write value.method(args...), unless the type of value is unnameable, it's always possible to instead write Type::method(value, args...). impl blocks does not imply anything semantically; any visual difference observed is entirely superficial.

7 Likes

There is no trait for floats in std though.

Broadly speaking, the Rust teams decided pre-1.0 that certain parts of the ecosystem made more sense as crates than as part of the standard library (which has very strict backwards compatibility constraints -- approximately "forever"). For example, one of those was regular expression support (regex). Another one was numerical traits (num).

Without a trait to dispatch off of like in @vague's example, sin can't be a single standalone function and instead there has to be a function (or method) per type. Those can't live in the same namespace (no standalone overloading). This could have been segmented by a standalone function per module, in which case you would call std::f32::sin(0.0) say. Or if the modules were added to the prelude, f32::sin(0.0).

Instead they're inherent methods, which is more flexible: you can do float.sin() or (&float).sin(), for example. And because the types are already part of the prelude, you can still do f32::sin(float) if you prefer.

Or you can use some crate that provides numerical traits.

23 Likes

The suffix (method) syntax also has a slight benefit in that it allows chaining function calls, which is often convenient when doing math.

5 Likes

What I also like on calculators is Reverse Polnish notation. (It's not exactly the same, because all arguments appear before the operation there, but I had to think of that.)

The oop style function call is much easier than functional style call in considering mind burden.

In the standard library, Rust also changed the names of some methods (e.g., expm1 in C is exp_m1, log is ln) and omitted some methods that are typically available in C (rint, the error function, everything related to the Gamma and Bessel functions).

Nah. Just no.

5 Likes

For me, it's much easier to call a lot of function in a chain with oop style, because the ide will give you the accurate auto completions in each step. Of course you can use pipe operator (some functional language support) to do the same thing, but AFAIK, there is no functional language's ide can give accurate auto completions for now.

1 Like

I have to respectfully disagree. Rust method notation looks very strange to me for mathematics, especially with functions that take multiple arguments, e.g. mul_add, atan2. It was technically convenient for Rust to use methods and that outweighed the burden of the change to very widely used mathematical notation.

1 Like

I prefer to write f64::sin(x) when calling the math methods.

13 Likes

But that's a different question. Writing Foo::method(foo) doesn't have any larger cognitive overhead by itself than writing foo.method().

1 Like

I am agree with you in calling a single function, but this would producing a terrible experience for calling three or more functions in a chain, because you need keep all the type information of each step returns in mind, and if the type or module is not in current scope, you need to import or quote it. This make me so tired in using functional languages.

3 Likes

Combining your (@alice's) answer and @vague's answer, you can apparently use:

num::Float::sin(1.23_f64)
num::Float::sin(4.56_f64)
4 Likes

Holy moly -- that's a lot of replies! Thanks everyone, I learned a bunch.

Worth noting (for my fellow noobs) that you have to add num as a dependency and declare extern crate num; in order to be able to do this, which is something you don't get from the playground link you shared.

You haven't needed that since edition 2018.

1 Like