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:


    • I'll start with the main, in my opinion, Rust features
    • I will describe interesting syntax details
    • Let me explain why Rust is not likely to take over the world.

    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.


    fnmain() {
        test();
        println!("End of main")
    }
    fntest() {
        let a = MyStruct {
            v: 1,
            s: Box::new(
                Some(MyStruct {
                    v: 2,
                    s: Box::new(None),
                })
            ),
        };
        println!("End of test")
    }
     structMyStruct {
         v: i32,
         s: Box<Option<MyStruct>>,
     }
     implDropfor MyStruct {
         fndrop(&mutself) {
             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:


    fnmain() {
        MyPrinter { value: 10 }.print();
    }
    traitPrinter {
        fnprint(&self);
    }
    impl Printer {
        fnprint(&self) {
            println!("hello!")
        }
    }
    structMyPrinter {
        value: i32
    }
    impl Printer for MyPrinter {
        fnprint(&self) {
            println!("{}", self.value)
        }
    }

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


    • Classes have no constructors. There are only initializers, which, using curly brackets, specify values ​​for the fields. If you need a constructor, then this is done through static methods.
    • The instance method differs from the static one by the presence of the & self reference as the first argument.
    • Classes, interfaces, and methods can also be generalized. But unlike Java, this information is not lost at compile time.

    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, as, 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:


    letmut 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:


    pubenumOption<T> {
        None,
        Some(T),
    }

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


    fnmain() {
        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:


    • Fans of functional programming will not be disappointed, for them there are lambdas. The iterator has methods for handling the collection, for example, filter and for_each . Something like a stream from Java.
    • The match construct can also be used for more complex things than ordinary enum , for example, for processing patterns.
    • There are a large number of built-in classes, for example, collections: Vec, LinkedList, HashMap , etc.
    • You can create macros
    • It is possible to add methods to existing classes.
    • Supports automatic type inference.
    • Along with the language comes the standard framework for testing
    • The built-in cargo utility is used to build and manage dependencies.

    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:


    • Box - an immutable value on the heap, an analogue of wrappers for primitives in Java
    • Cell - variable value
    • RefCell - variable value available by reference
    • Rc - reference counter, for several links to one object

    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 >>> :


    • Option - to handle an empty link
    • Rc - for several links, because the last object is referenced by the previous node and the sheet itself
    • RefCell - for a changeable link
    • ListNode - the next item itself

    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:


    structListNode {
        val: i32,
        next: Node,
    }
    pubstructLinkedList {
        root: Node,
        last: Node,
    }
    typeNode = Option<Rc<RefCell<ListNode>>>;
    impl LinkedList {
        pubfnadd(mutself, 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:


    publicfunadd(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. when moving 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.

    Only registered users can participate in the survey. Sign in , please.

    Do you think it is worth switching to Rust?


    Also popular now: