📜 ⬆️ ⬇️

First Rust Steps

image


Hello. Recently met with a new programming language Rust. I noticed that it is different from the others I have come across before. Therefore, I decided to dig deeper. I want to share the results and my impressions:



Immediately I will explain that I have been writing in Java for about ten years, so I will argue from my bell tower.


Killer feature


Rust is trying to take an intermediate position between low-level languages ​​like C / C ++ and high-level Java / C # / Python / Ruby ... The closer the language is to the hardware, the more control, it is easier to foresee how the code will be executed. But having full access to the memory is much easier to shoot yourself a leg. In contrast to C / C ++, Python / Java and all the rest appeared. They do not need to think about cleaning up the memory. The worst trouble is the NPE, leaks are not a common occurrence. But for this all to work, at least garbage collector, which in turn begins to live its own life, in parallel with the user code, reducing its predictability, is necessary. The virtual machine still provides platform independence, but as far as is necessary - a controversial issue, I will not raise it now.


Rust is a low-level language, the output of the compiler is a binary, for which no additional tricks are needed. All logic for removing unnecessary objects is integrated into the code at the time of compilation, i.e. garbage collector at runtime either. Rust also has no null links and the types are safe, which makes it even more reliable than Java.


At the heart of memory management is the idea of ​​owning a reference to an object and borrowing. If each object is owned by only one variable, then as soon as its life ends at the end of the block, all that it pointed out can be cleaned recursively. Links can also be lent for reading or writing. Here the principle of one writer and many readers work.


This concept can be demonstrated in the next piece of code. From the main () method, test () is called, in which a recursive data structure MyStruct is created , which implements the destructor interface. Drop allows you to set logic to execute before an object is destroyed. Something similar to the finalizer in Java, but unlike Java, the moment of calling the drop () method is quite definite.


fn main() { test(); println!("End of main") } fn test() { let a = MyStruct { v: 1, s: Box::new( Some(MyStruct { v: 2, s: Box::new(None), }) ), }; println!("End of test") } struct MyStruct { v: i32, s: Box<Option<MyStruct>>, } impl Drop for MyStruct { fn drop(&mut self) { println!("Cleaning {}", self.v) } } 

The output will be as follows:


 End of test Cleaning 1 Cleaning 2 End of main 

Those. memory was recursively cleared before exiting test () . The compiler took care of this by inserting the necessary code. What is Box and Option I will describe later.


Thus, Rust takes security from high-level languages ​​and predictability from low-level programming languages.


What else interesting


Next, I will list the features of the language in descending order of importance, in my opinion.


OOP


Here Rust is generally ahead of the rest. If most languages ​​have come to the conclusion that it is necessary to abandon multiple inheritance, then in Rust there is no inheritance at all. Those. a class can only implement interfaces in any quantity, but cannot be inherited from other classes. In Java terms, this would mean making all classes final. In general, the syntactic diversity to maintain OOP is not so great. Perhaps this is for the better.


To merge data there are structures that can contain implementation. Interfaces are called trait and may also contain default implementations. They do not reach the abstract classes, because cannot contain fields, many complain about this restriction. The syntax is as follows, I think the comments are not needed here:


 fn main() { MyPrinter { value: 10 }.print(); } trait Printer { fn print(&self); } impl Printer { fn print(&self) { println!("hello!") } } struct MyPrinter { value: i32 } impl Printer for MyPrinter { fn print(&self) { println!("{}", self.value) } } 

Of the features that I noticed, it is worth noting the following:



A little more security


As I said Rust pays great attention to the reliability of the code and tries to prevent most errors at the compilation stage. For this, the possibility of making links empty has been excluded. It reminded me of the nullable types from Kotlin. Use Option to create null links. Just as in Kotlin, when you try to access such a variable, the compiler will beat your hands, forcing you to insert checks. Attempting to pull out a value without checking may result in an error. But this certainly can not be done randomly, for example, in Java.


I also liked the fact that all variables and class fields by default are immutable. Hello Kotlin again. If the value can change, it is obviously necessary to specify the keyword mut . I think the desire for immutability greatly improves the readability and predictability of the code. Although Option is for some reason mutable, I did not understand this, here is the code from the documentation:


 let mut x = Some(2); let y = x.take(); assert_eq!(x, None); assert_eq!(y, Some(2)); 

Transfers


In Rust are called enum . Only besides a limited number of values, they can still contain arbitrary data and methods. Thus, it is a cross between enumerations and classes in Java. The standard enum Option in my first example just belongs to this type:


 pub enum Option<T> { None, Some(T), } 

To handle such values ​​there is a special design:


 fn main() { let a = Some(1); match a { None => println!("empty"), Some(v) => println!("{}", v) } } 

And


I do not intend to write a textbook on Rust, but just want to emphasize its features. In this section I will describe what else is useful, but, in my opinion, not so unique:



Spoons of tar


This section is required to complete the picture.


Killer problem


The main drawback comes from the main feature. You have to pay for everything. In Rust, it is very inconvenient to work with variable graph data structures, since Any object should have no more than one link. To circumvent this limitation, there is a bunch of built-in classes:



And this is an incomplete list. For the first sample of Rust, I rashly decided to write a single-linked list with basic methods. Ultimately, the link to the node was the following Option <Rc <RefCell <ListNode >>> :



It looks so-so, a total of three wrappers around one object. The code for simply adding an item to the end of the list was very cumbersome, and there are some unobvious things in it, such as cloning and lending:


 struct ListNode { val: i32, next: Node, } pub struct LinkedList { root: Node, last: Node, } type Node = Option<Rc<RefCell<ListNode>>>; impl LinkedList { pub fn add(mut self, val: i32) -> LinkedList { let n = Rc::new(RefCell::new(ListNode { val: val, next: None })); if (self.root.is_none()){ self.root = Some(n.clone()); } self.last.map(|v| { v.borrow_mut().next = Some(n.clone()) }); self.last = Some(n); self } ... 

On Kotlin, the same thing looks much simpler:


 public fun add(value: Int) { val newNode = ListNode(null, value); root = root ?: newNode; last?.next = newNode last = newNode; } 

As I found out later, such structures are not characteristic of Rust, and my code is completely non-idiomatic. People even write whole articles:



Here Rust sacrifices readability for safety. In addition, such exercises can still lead to looped links that hang in memory, because no garbage collector will clean them. I didn’t write working code on Rust, so it’s hard for me to say how much such difficulties complicate life. It would be interesting to get comments from practicing engineers.


Difficulty learning


The long process of studying Rust largely follows from the previous section. Before you write anything at all, you will have to spend time mastering the key concept of memory ownership, because it permeates every line. For example, the simplest list took me a couple of nights, while on Kotlin the same thing is written in 10 minutes, despite the fact that this is not my working language. In addition, many familiar approaches to writing algorithms or data structures in Rust will look different or not work at all. Those. in going to it, a deeper restructuring of thinking will be needed, just mastering the syntax will not be enough. This is not JavaScript, which swallows everything and endures everything. I think Rust will never be the language in which children learn in programming school. Even C / C ++ has more chances in this sense.


Eventually


The idea of ​​memory management at compile time seemed very interesting to me. In C / C ++, I have no experience, so I will not compare it with a smart pointer. The syntax is generally pleasant and there is nothing superfluous. I criticized Rust for the complexity of implementing graph data structures, but I suspect that this is a feature of all programming languages ​​without GC. Perhaps the comparison with Kotlin was not entirely fair.


Todo


In this article, I have not touched multithreading at all, I think this is a separate big topic. There are also plans to write some kind of data structure or algorithm more complicated than the list, if you have ideas, please share in the comments. It would be interesting to find out what type of applications they write on Rust.


Read


If you are interested in Rust, then here are some links:



UPD: Thank you all for the comments. I learned a lot of useful things for myself. Corrected inaccuracies and typos, added links. I think such discussions greatly contribute to the study of new technologies.


')

Source: https://habr.com/ru/post/433302/


All Articles