Archive | Rust RSS for this section

Of Semicolons and Sadness

Lately, I’ve been writing a lot of Haskell. If you’ve been following the blog, you likely are aware of this. However, I’ve been feeling the itch to try something different. I’ve been dabbling in things here and there, but for some reason I just can’t settle into a groove. It seems that Haskell has ruined programming for me.

Why? Is it the functional purity of Haskell? Is it the way that lazy evaluation changes the way you think about programming? Is it the sublime type system that catches whole classes of bugs at compile time? No, I’m afraid not. My issue is much more basic; semicolons and angle brackets.

Your Mission

…should you choose to accept it: write a function fun that does the following: takes an integer argument acc, and two arguments of the same generic type. This function should enforce that the two generic arguments cannot be changed. This function calls another function given that takes two arguments of the same generic type, that also cannot be changed. given does something, then returns an integer as a result. given can fail!

After calling given, if it failed, return acc - 1, if it succeeded, return its result + acc.

In C

C is a pretty simple language. I like simplicity (it’s the whole premise of this post, right?) but C’s simplicity turns into overly verbose code in the face of the requirements of fun. Here is the signature of given

// please ensure that lhs and rhs have the same type int given(int * result, void const * const lhs, void const * const rhs)

So, what’s going on here? In C, the dominant idiom for indicating success or failure is to return an error code, thus we return an int. However, this isn’t our return value! We use the out parameter result instead. Finally, we have lhs and rhs, which have the confusing types void const * const. These read as “constant pointer to constant void”, and ensures that neither the where the pointer points, or the value of what it points to can change. (thus documenting the requirement that it not change its arguments!) Finally, we have our comment asking the caller to pretty please, ensure lhs and rhs have the same type. There is no way in C to ensure two void * are actually the same type, so this is the best we can do… Now, let’s see my implementation of fun:

// please ensure that lhs and rhs have the same type int fun(int acc, void const * const lhs, void const * const rhs) { int res = 0; if (given(&res, lhs, rhs)) { return acc + res; } else { return acc - 1; } }

We have the same confusing sort of signature, except here the return value is our actual return value, and acc is not an out parameter. (which is really obvious, right?)

Inside of this function, we allocate res on the stack and initialize it to 0. Then we call given with the address of res, and lhs and rhs. Taking the address of a something requires a special symbol. This function is called within an if block because given returns 0 on failure and something on success.

Some of this noise can be reduced, but overall it’s pretty minimal. The use of void pointers and our type checking comment are troubling, but that’s not really the point of this post. However, the many semicolons, curly braces, and commas add a lot of clutter.

Noise level: Moderate
Strangeness: High
Confidence that there are no bugs in my implementation: Very Low

In C++

Next, let’s check out some C++. Here is the signature of given:

// throws an std::exception on failure! template <typename T> int given(const T & lhs, const T & rhs)

To me, this is kind of a wash from the C implementation. On one hand, we have this template <typename T> silliness, on the other hand we don’t have an out parameter, we only have one const per argument, and the compiler can actually enforce that lhs is of the same type as rhs. Next, the implementation:

template <typename T> int fun(int acc, const T & lhs, const T & rhs) { try { return acc + given<T>(lhs, rhs); } catch (std::exception & e) { return acc - 1; } }

Here we have that same yucky sort of signature, and we have these try and catch blocks. Personally, I don’t care for exceptions. However, they are a tried and true strategy accepted by the majority of the programming community, and who am I to dispute that? However, what I don’t like is the fact that we have no indication that given can throw. We had to read a comment. Additional weirdness here is the call to given: given<T>(lhs, rhs). Why do I have to do <T>?

The usual complaints about curly braces and semicolons apply here as well.

Noise Level: High
Strangeness: Low
Confidence that there are no bugs in my implementation: Low

In Rust

A newcomer to the scene is Rust. Rust is a low level systems programming language that has a very strong emphasis on safety. Excellent, right? Let’s see the signature of given:

fn given<T>(lhs: &T, rhs: &T) -> Option<i64>

… eew. We have twice as many angle brackets as the C++ implementation did, which is unfortunate. We’ve also added some colons. On the plus side, we have option types in rust, so no icky exceptions! Let’s see my implementation of fun:

fn fun<T>(acc: i64, lhs: &T, rhs: &T) -> i64 { let res = given(&lhs, &rhs); let ret = match res { Some(x) => acc + x, None => acc - 1, }; ret }

… and things get strange. Let’s go through this line by line:

fn fun<T>(acc: i64, lhs: &T, rhs: &T) -> i64: A bit noisier than I’d like, but perfectly reasonable.

let res = given(&lhs, &rhs);: Here we call given, and for some reason I have to put an & in front of lhs and rhs. At least rustc inferred the type for us!

let ret = match res {};: Yay, Rust has pattern matching! It’s a bit unfortunate about all those curly braces and semicolons though…

Some(x) => acc + x,: Nothing wrong here, in a world where commas and such are a thing…

None => acc - 1,: And here we have the last pattern of this match block. Why is there a comma there?! For reasons that science will never explain, in Rust, you have to put commas after the last entry in things like this! This oddity extends to structs and enums as well…

ret: Finally, we return. No, ret isn’t a keyword, it’s a variable that I bound in my pattern match. Notice the lack of a semicolon. In Rust, the last line in a function is the return value, and does not have a semicolon! However, rust has a return statement for early returns, and this does take a semicolon. Yes, you can use a return statement here, but according to the docs doing this makes you a bad person.

Noise Level: Very High
Strangeness: High
Confidence that there are no bugs in my implementation: Reasonable

In Haskell

Finally, let’s take a look at what I consider to be a reasonable syntax: Haskell. The signature of given is as follows:

given :: a -> a -> Maybe Int

Once you learn to read Haskell, you’ll see how simple this is. given is a function that takes an a, another a, and returns a Maybe Int. Next, let’s see my implementation of fun:

fun :: Int -> a -> a -> Int fun acc lhs rhs = case given lhs rhs of Just res -> acc + res _ -> acc - 1

Not counting the optional function signature, this is 3 lines. The first is our pattern match, which calls given. The second line handles success, and the third handles failure. There is no return statement here, the result of the expression is the return value. And unlike Rust, there’s no special case that breaks the mold.

Noise Level: Low
Strangeness: Low (If your definition of strange isn’t “doesn’t look like C”)
Confidence that there are no bugs in my implementation: High

Which Brings Me To My Point

Honestly, I believe that the reason for all of this is: “We’ll make our language look like C so that we don’t scare people away!” Personally, I believe that this is a sad state of affairs.

Strictly speaking, I do mean to criticize. C, C++, and Rust are all fine languages. They do what they set out to do, and do it well. Many people successfully use these languages every day! They are all languages worth knowing and languages worth using. However, there is no reason for all these glyphs and extra keywords!

My top feature for the new version of C++: Get rid of all uses of the angle bracket!

My top feature for the new version of Rust: Get rid of those trailing commas and the return statement!

Of course this will never happen, and for good reason: It will cause massive breakages in everybody’s codebases. But a man can dream…

%d bloggers like this: