This is my second approach to learn Rust and I think now I am starting to get it. That is I am starting to really like Rust.
Cargo is great. Simply great. It might actually start a new cult.
null
is dead! Long live Option
!I really like that there is no null
in Rust. When a value can be missing then one has to use Option
. It makes the code more explicit because one has to “unpack” the real value which forces to perform the null check (a bit similar to how nullability is made explicit in Kotlin).
What makes Option
(and Result
) such a great solution is how Rust does pattern matching and enums. Go read about it!
Another great thing about Rust. It does not have type conversions on the language level, instead it has them implemented in standard library as Into
and From
traits.
One implements From
for new types to mark as they can be created from given things aka values of this type can be created From X, Y, Z.
And then uses Into
either as a type for function arguments (this function accepts all types that can be converted Into X) or calls .into()
when passing an argument to make it Into what the function accepts.
As with the type conversions, the language is kept really small. For example it doesn’t have random number generation! It used to be a part of standard library but was deprecated and then removed. Now it lives in a crate.
Rust is hard and I mean hard. I have been programming more than a half of my life and using some crazy languages but programming in Rust brings me back to the beginnings.
That said it is getting better. Compiler is a friend and tries to help (and not just vomits pages of resolved templates). It actually has a page for each error with explanation what might have caused it and how to potentially fix it - teaching you Rust as you go.
The borrow checker again. Take a look at this code. Nothing suspicious right?
fn main() {
let mut vec = vec![1, 2, 3];
vec.push(vec.len());
}
Well, let’s give it a go:
$ rustc double_borrow.rs
error[E0502]: cannot borrow `vec` as immutable because it is also borrowed as mutable
--> double_borrow.rs:3:14
|
3 | vec.push(vec.len());
| --- ^^^ - mutable borrow ends here
| | |
| | immutable borrow occurs here
| mutable borrow occurs here
error: aborting due to previous error
BOOM!
push
is actually*:
pub fn push(&mut self, value: T)
So first there is a &mut
borrow on vec
to evaluate first argument and then another &
borrow for calling len
on the same vec
. Mutable and immutable borrows at the same time - that’s a no-no in Rust land.
To fix this one can structure the code like this:
fn main() {
let mut vec = vec![1, 2, 3];
let len = vec.len();
vec.push(len);
}
So the length is stored in a temporary variable and there is only one borrow at a time.
But why stop here? If you find yourself using this kind of construct all the time then you can use some other Rust magic. Another, more creative, solution would be to give each type a super power of pushing itself into a vector:
trait PushInto<T> {
fn push_into(self, v: &mut Vec<T>);
}
impl <T> PushInto<T> for T {
fn push_into(self, v: &mut Vec<T>) {
v.push(self)
}
}
fn main() {
let mut vec = vec![1, 2, 3];
vec.len().push_into(&mut vec);
}
Again only one borrow is done at a time.
It is not that bad as it sounds. The compiler is getting smarter and in Rust 2018 the first version of the code compiles without problems.
tags: rust