Structs & enums

I got yet another chunk of free time for Rust.

I’d like to say few things about Rust in generic before continue into the struct chapter. Few rather long threads on Rust in my Russian-language blog come to very specific point: How much is ownership/borrow model useful in real life? They pointed to doubly linked list which couldn’t be implemented in a pure ownership/borrow model. If we have A pointing to B, and B pointing to A, it’s impossible to say who own whom, according to my interlocutor. I found this reddit thread which pointed to this code. It’s too complicated for me to understand at my current level, therefore I just trust it: it’s possible to made a doubly linked list without unsafe features.

Step back to tuples

I browsed through tuples earlier, and when they were noted again in later chapter I realize I didn’t pay enough attention to them.

  1. Tuple are written in round brackets and may contain values of different types.
  2. Tuple elements are accessed by ‘.’.

As usual, I couldn’t store str type in tuple as str has no trait for ‘std::marker::Sized’. Now I understand why. Str describes some fixed memory location, and I couldn’t store ‘memory location’ inside other memory location. It’s like storing a land inside a house. We can store papers on the land inside a house, we can say that land is occupied by a house, but we physically couldn’t store land inside a house. Same for str. It’s like a land. You may use it, you may pretend to own it (&str), but most operations (like relocation, disposal, etc) aren’t possible.

Tuple elements are mutable (to a big surprise for pythonist):

fn foo(v: i8) -> i8 {
let mut x: (&str, i8) = ("foo", 2);
x.1=2;
x.1=v;
x.1
}

I couldn’t use ‘inner tuple’ code:

let mut x: (&str, (i8, i8)) = ("foo", (2, 3));
x.1.1=2;

First line is valid, second is invalid. Oh, Rust says me it doesn’t understand order for operation ‘.’. Code below works:

fn foo(v: i8) -> i8 {
let mut x: (&str, (i8, i8)) = ("foo", (2, 3));
(x.1).1=2;
(x.1).0=v;
(x.1).0
}

I couldn’t reassign types on ‘inner tuples’:

    let mut x: (&str, (i8, i8)) = ("foo", (2, 3));
let y: (&str, &str) = ("foo", "bar");
x.1 = y;
...
(expected i8, found &str)

I couldn’t do this even if data types have same size: (expected i8, found u8).

I couldn’t change length for inner tuple (expected a tuple with 2 elements, found one with 3 elements).

So, tuples are immutable as composite type, but allow value modifications. As length is a property of tuple type, I couldn’t put longer tuple instead of the shorter one.

I couldn’t put array inside of the tuple: (the trait `std::marker::Sized` is not implemented for `[i8]`), but I could otherwise:

fn foo(v: i8) -> i32 {
let mut a = [(1,2), (3,4)];
a[0] = (5, 6);
a[1].1 = 4;
a[0].0
}

At the same time tuples of different sizes aren’t allowed (expected a tuple with 2 elements, found one with 3 elements).

I can copy tuples with single operation:

let mut a  = ((1,2), (3,4));
a.0 = a.2;

As usual I got interest how large could be tuple.

10k-tuple code compiled with a slight delay. 100k took 2.2 seconds, and 1M cause compiler to spend about 21s seconds. Just for fun I done 10M elements, even my python script took few seconds to generate Rust code. Compilation took about 4 minutes (3:52) — 232 seconds. As we can see, compilation happens at rate of 23 microseconds per tuple element. Rust didn’t failed in process, and stripped resulting (debug) binary was 96Mb in size. Big tuples of constants may be required for storing data in binary files, so ability to accept long tuples is important.

Struct

I’ve returned back to struct chapter with restored knowledge of tuples.

One thing I miss in Python is structs. No class-tricks could help, as I really like to have easy access to members of the structure by name without ugly dict notation (config[‘section’][‘variable’]). Rust has structs.

But the first struct example cause me wounder: why struct can be used without ‘;’ at the end? This is type declaration, I understand that. But type declaration is a sentence, isn’t it? I’ve tried to add ‘let x = Struct …’, but Rust says it expects expression, and I used keyword struct. I posted this question to SO.

Good news that I could use ‘;’ after struct.

Next example (one which shows how to use struct as argument to function) give me one more insight on Rust: I can use functions without initial declaration! Such simple thing. C hasn’t that feature due to it frailty, Python — due to it way to deal with functions and decorators. And Rust, finally, allows such simple convenience.

It also can be applied to structs. I moved struct section down to the file and it compiled successfully.

I also found that stuct is bound by scope:

fn main() {
let rect1 = Rectangle { length: 50, width: 30 };
println!(
"The area of the rectangle is {} square pixels.",
area(&rect1)
);
{
struct Rectangle {
foo: &str,
}
}
}
fn area(rectangle: &Rectangle) -> u32 {
rectangle.length * rectangle.width
}
struct Rectangle {
length: u32,
width: u32,
}

Traits!

Finally! Traits are noted in tutorial! I saw them so many times in error messages and outside of tutorial, that it become some kind of unreachable itch: It reminds about itself again and again, and I couldn’t do anything with it. Now I got a stick to scratch it.

… nope. They didn’t give me a stick. They gave me a mysterious #pragma reunobfuscate-latteral, …I mean #[derive(Debug)]. I somehow guessed that it would build trait Debug for a given struct which in turn would be used by println!, but it’s a guess. Traits are still unscratched. And they would until chapter +5 from now.

Methods

Instead of explaining traits they gave me methods: functions ‘bounded’ with types. No classes involved. It’s the first time I see word ‘method’ outside of OOP and classes.

… their (methods) first parameter is always self, which represents the instance of the struct that the method is being called on.

And they say me that ‘instance of the struct’ is somehow differ from ‘instance of the class’?

And now it goes. Pesky impl which I saw in virtually every Rust source code is a way to declare methods for structs.

What can I do with impl? I can put more than one method in one block — checked. I can made two blocks for same struct. I can’t redefine one method defined in one impl within other impl.

Assosiated functions are exactly like I expected: replacement for __init__. As __init__ is called when class is instantiated, asssosiated function may be called to return instance of struct. But there is a big difference: __init__ is called (with selfalready created), and associated function may return an instance of the struct. Or may not, and that gives us more flexibility.

Back to Semicolon

Meanwhile I got an answer on my SO question “why struct declaration may ommit ‘;’?”. It’s a rather interesting list of comments and I appreciate a full answer. When I had reasoned earlier, I had missed one thing: semicolon shouldn’t be placed after struct declaration outside of function.

In short: a struct is like a function — it has body and it does not need ‘;’ Every element in language of such type does not need it.