simple things

sad news: Due to personal reasons I may slow down. I’ll try to keep high pace, but I’m not sure if I would be able to.

Today I’ll follow this (next) chapter on simple data types and control flow.

Keywords

They noted a list of Rust keywords and I decided to browse it and think about each of them. Not like I understand all of them, but still…

const — we have mut and without it variable is immutable. Yet, we have const. As far as I can understand, it’s used for constant values, and pointers. Yaik! Scary scary pointers! The are in Rust. NULL reference twice to everyone!

true/false uses lower case.

for … …and is a part of higher-ranked lifetime syntax. It sounds like a spell. I found some on this:

Ownership is how Rust achieves its largest goal, memory safety. There are a few distinct concepts, each with its own chapter:
ownership, the key concept
borrowing, and their associated feature ‘references’
lifetimes, which you’re reading now

I’ll leave it for now without future reading. Too complicated.

self and Self. They are different keywords! OMG. Seriously? I’d say this is first inexcusable oddity in Rust.

A rather modest list it was. Reserved keywords have no explanation and they leave space for imagination: A final box sizeof pure virtual abstract yield.

… wait, yield, you say? Generators? Python generators I want!

Constants

My earlier thoughts about how unusual mut is, now irrelevant. We have const in Rust and there is a clear explanation in tutorial why mut is needed. It’s just an addtional (un)restriction placed on a variable. It has nothing to do with constancy of a value and it just permit changes of variable. Without mut Rust places more restrictions on variables, that’s all.

As usual I’ve tried to break constancy. Got a few peculiar errors, but my Rust knowledge is still to low to really mess with strings, and my play with integers didn’t give me anything unusual. A note for the future: I can’t have String constant, but I can have &str constant.

… and I like this warning: constant `x` should have an upper case name such as `X`.

Data types

i8, i16, i32, i64, i128…

error: 128-bit type is unstable (see issue #35118)

I feel myself a bit disappointed by lack of true integers (without upper bound). I understand that such datatype wouldn’t be register-friendly, but nevertheless, IMHO, complex numbers and true integers are ‘must have’ in any language at compiler level.

b’A’ notation to encode ASCII symbols is a clever way to keep ‘letters are numbers’ approach of C. It’s good. I occasionally really needed it in Python. At the same time it’s just a notation, not a real ‘character is a byte’, so everyone is happy.

I’ve tried to do this: const x: u32 = b’FOO3'; but it was rejected as error: unterminated byte constant: b’F. Then I realized that supporting u32 with characters would open Little Endian Hell to most and Big Endian Hell to some. This is the reason why Rust allows this notation be used only with u8 datatype. Any longer datatype will cause Endian hell with leading (trailing?) zeroes. But I found that documentation is slightly incorrect. Rust allows to use characters outside of ASCII (which is 7-bit encoding system), and documentation says that this notation is limited only to ASCII symbols:

const x: u8 = b'\xFF';

Floating points is another funny area to play with. const x: f64 = 1; is not accepted, expected f64, found integral variable. When type is explicitly specified I see no reason why this should be an error.

NaN and inf are not accepted as floating, but they can be defined via constexp:

const X: f64 = 0.0/0.0;
... The value of x is: NaN
const X: f64 = 1.0/0.0;
... The value of x is: inf

There is absolutely no typecasting for int into float: 1.0/1 is error (the trait `std::ops::Div<{integer}>` is not implemented for `{float}`). I have some nasty experience with C sudden conversion from/to floats, so I cheer this.

Output for floatings is funny. It never (?) uses scientific notation.

const X: f64 = 0.000000000000000000000000000000000000000000000000000000000000001/100000000000000000.0;
The value of x is: 0.000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000001

(It looses precision, but this is not a Rust issue)

I like warning: literal out of range for f32 for values too small (by module) to be stored in type, but I found a clear bug for constant expressions:

const X: f32 = 0.00000000000000001/100000000000000000000000000000.0;
...
The value of x is: 0

As const expressions are calculated at compile time, compiler should be able to see problem and report it at least as a warning. I’ll raise a bug for this.

Character definition for char (one unicode symbol) is a clear victim of 💩. Very inconsistent. For example, it allows to use hex notation but only once. I couldn’t write emoji as:

const X: char = ‘\xf0\x9f\x92\xa9’;
...
error: this form of character escape may only be used with characters in the range [\x00-\x7f]

As far as I understand there is no big need for this type as constant or variable initialization. Normal string with one character will do a job. But it’s really useful when someone decided to iterate over utf-8 string. Python in such situation return ‘one-character string’. Special type for iterator variable is a great addition, as the more specific type it has, the less generic errors it causes.

Composite types

End of macros power starts here. I couldn’t compile this code:

fn main() {
const X: (i32, char) = (3, '3');
println!("The value of x is: {}", X);
}
--> src/main.rs:3:39
|
3 | println!("The value of x is: {}", X);
| ^ the trait `std::fmt::Display` is not implemented for `(i32, char)`
|
= note: `(i32, char)` cannot be formatted with the default formatter; try using `:?` instead if you are using a format string
= note: required by `std::fmt::Display::fmt`

Unfortunately, it’s not a Python with ductyping (if it has __str__ or __repr__ we can print it), and list processing is a bit different here…

Tuple elements handling is a second oddity for Rust. Dot for access by index? …On another hand it’s not counter-intuitive (at least not more than Python way to use dictionary access by key and list access by index by the same []).
Array left me with syntax confusion, as I wasn’t able to figure how to construct constant array, and to declare type of arrays content for variable. (default is i32).

And there is no negative indexes. Sad.

Functions

I don’t know if tutorial skipped details or Rust really has no default parameters in functions. If it hasn’t, this would be a devastating blow to my sense of aesthetic. Same goes arguments names. One thing I find brilliant in Python is it’s ability to use argument names during function call:

UglyFunction(
object = foo,
path = '/etc/foo/bar',
dumper = json.loads,
logger = log,
state = 'existed',
)

It even become less ugly in su notation… Where is this in Rust?

… expression with ‘;’ and without it. Without ‘;’ it is an expression, with it — statement. I find this extremely annoying. So far it’s the most annoying thing I saw in Rust. It will lead to thousands stupid typo. I don’t know if it will lead to runtime bugs or not, but many curses at compile time are guaranteed.

Once more I’ve tried to fight type safety in Rust. I’ve tried to force it to situation when variable would be undefined or return value would be omitted. I failed. My closest attemp looks like this:

fn foo(a: i32, b: i32) -> bool{
loop {
let x = match a == b {
true => {break;},
false => {return false;}
};
return x;
}
true
}

If only I was able to get out of match to same level where return is… My other failed attempt:

fn foo(a: i32, b: i32) -> i32{
let x:i32 = match a == b {
true => {let x: bool = true; 1},
false => {let x: bool = false; 2}
};
return x;
}

The best WTF I was able to make:

fn foo(a: i32, b: i32) -> i32{
let x:i32 = match a == b {
true => {return 3;},
false => 4
};
return x;
}

We kinda have x possibly undefined after match, but this is an illusion, as ‘undefined’ branch (with return 3;)would never reach return x line.

After half-hour of probing this problem (I actually tried much more than those three examples), I’m not only started to trust Rust’s ability to calculate paths and types, but also got better sense of what is ‘scope’, how match works (those returns, and loops, and breaks inside match…). I got few times error: match arm with an incompatible type, witch close me opportunity to mischief.

Control flow

Very simple. I like they rid of C-style () in conditions, and I respect that Rust does not autoconvert anything to bool. A bit annoying, but safe.

for … in operator is a bit of explicit disappointment. Python can call for ‘iter()’ method by itself. Rust need you to specify it:

for element in a.iter() {...}

And talking about iterators:

for x in (“a”, 3).iter() {println!(“The value of x is: {}”, x)};

Unfortunately, strict ‘no’ to this.

Summary

This part was simple. Most of those features are obvious or expected. I find tuples and arrays a bit strange, but rest is trivial. Oh, yes, where is my ‘goto’?

Like what you read? Give George Shuklin a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.