How to set up smartphones and PCs. Informational portal
  • home
  • Iron
  • Rust programming language. Overview of the Rust programming language

Rust programming language. Overview of the Rust programming language

In 2013, Mozilla, together with Samsung, announced the development of a new web browser engine, Servo. It was created specifically for multi-core processors of mobile devices, is capable of breaking tasks into parallel threads and greatly reducing the loading time of web pages. Servo is written entirely in the Rust programming language, which Mozilla developed itself for writing mobile applications.

About the language

Rust is a procedural programming language that supports a variety of coding styles. Developer Graydon Hoare began creating the language in 2006, and three years later Mozilla joined the project. In 2010, Rust was presented at the Mozilla Summit conference. In the same year, development was transferred to a compiler written in Rust. The compiler used the universal program analysis and transformation system LLVM as a database.

The first stable version of the language was released in 2015. After the release of the alpha version, Rust underwent changes - only ready-made features were left inside the compiler that will not change. Everything else was moved to the experimental section.

At the heart of the language, Graydon Hoare laid down such concepts as:

  • Safety. Rust contains a number of programmer restrictions that are enabled by default. To disable them, the “unsafe” label is required in blocks and functions.
  • Speed. The language is comparable in speed to another programming language, C++, which provides a clear number of advantages for the programmer.
  • Parallelism. The system can perform several calculations simultaneously, and at the same time they can interact with each other.
  • Brevity. The first keywords in Rust were limited to five characters. But later this restriction was lifted.

An example of one of the first codes in Rust

However, Rust is not without its drawbacks, the most striking of which are:

  • Code redundancy.
  • Lack of literature for language learning.
  • Clarity in entering compilation parameters. This does not always suit experienced programmers, since other languages ​​do not have similar rules.

However, the language is regularly modernized and expanded: its updates are released every 6 weeks.

Comparing Rust with C++

The creators of Rust consider it the successor to C++, which emerged in the early 1980s when the developer came up with several improvements to the C language. Therefore, it is worth comparing the young and still changing language with the time-tested one.

  • Accessing remote memory. In C++, deleting a variable can cause a number of problems. Such complications are not possible in Rust, since it does not have commands for deleting memory. The descendant compiler will report that the code you wrote contains an error, and the C++ compiler will output the result without the removed values, without even reporting the problem.
  • Semicolon. Adding an extra semicolon to your code will cause an error in C++, whereas in Rust the loop body is enclosed in curly braces.
  • Unsafe code. Rust has an "unsafe" label that isolates the main code from the unsafe code. In the future, when reviewing code, this allows you to narrow down the search for vulnerabilities.

It was in C++ that Firefox was implemented: this capricious language required increased attention to detail. Otherwise, errors turned into serious vulnerabilities. Rust was designed to solve this problem.

Prospects

In the RedMonk ranking for the third quarter of 2018, the Mozilla programming language consistently ranks 23rd. Experts believe that his position is not in danger of improving. Despite this, in August 2018 the creators released the updated Rust 1.28.

After the release of Rust in 2015, according to Stack Overflow, 74% of developers wanted to get acquainted with it. However, already in 2016, it moved to first place: 79% of users named Rust as their favorite programming language and expressed a desire to continue working with it. Rust took first place in this parameter in 2018.

Stack Overflow is a popular programming question and answer system developed in 2008.

The popularity of Rust is confirmed by the number of companies using it in their development. Currently, this list includes 105 organizations.

So, we would like to introduce to your attention a recent birthday boy (he turned one year old on May 15, 2016) - Rust. This is a universal programming language developed by Mozilla, whose three main principles are: speed, security and ergonomics. The creators themselves immodestly consider it one of the most likely successors of C/C++. According to a survey on StackOverflow, Rust is the most favorite language among developers today. So, let's take a closer look at what it is.

Rust for a beginner

I don’t want to deceive anyone, so here’s a responsible statement: Rust is quite difficult to learn. Firstly, this is due to the youth of the language and, as a consequence, the small amount of literature. Secondly, it may be even easier for a person far from programming to learn it than for someone familiar with other languages. So, for example, a ready-made IT specialist will be greatly annoyed by the need to prescribe the slightest operations, and the absence of inheritance as such in the language will simply confuse him.

However, Rust is developing rapidly (a new release is released every 6 weeks), the community is growing, and finding information on the Internet is no longer difficult.

How to study

You can find almost everything you need on the official website. In addition, the community of Rust followers is very large and friendly, so you can always turn to IRC (there is a Russian section) and the official forum for advice. In addition, books, including electronic ones, began to appear little by little. It is still difficult to assess their quality, but this is a fact.

For those who have passed the initial stage of getting acquainted, you can find a lot of useful material on GiHub, including RFCs and commits. In addition, you can attend in person or at least watch the webcast of one of the Rust conferences scheduled for the second half of the year. Here is the calendar:

  • September 9-10 RustConf conference in Portland, USA;
  • September 17, RustFest European Communities Conference in Berlin, Germany;
  • October 27 Rust Belt Rust conference in Pittsburgh, USA;

Well, to meet those who consider Rust their calling, and at the same time ask them for all their wisdom, you can contact the organizers and participants of meetings of language fans in Moscow.

Peculiarities

Duplicating a little what was said earlier, we will highlight the main pros and cons of the Rust language.

Pros:

  • Safe work with memory;
  • High performance;
  • Algebraic data type;
  • Predictability of compilation;

Minuses:

  • Some code redundancy;
  • High intensity of language development and, as a consequence, lack of good relevant literature for study;
  • The need to clearly and unambiguously specify parameters for compilation.

In fact, you quickly get used to differences, such as replacing inheritance with abilities. As soon as your eyes get used to it and your hands get used to it, Rust turns into a completely working language, simpler and more functional than C++, but inferior in “beauty” to many other programming languages. In fact, the main difference between Rust and its competitors and predecessors is speed and security.

Demand

Today, Rust is popular among game, graphics, and operating system developers. However, for obvious reasons, the number of stationary places where highly specialized Rust experts are required is extremely small in the world, and even more so in Russia. However, there are no signs yet that the language will sink into oblivion; it looks more like a systematic takeover of the world. This means that good skills in using Rust in the future will help you find a well-paid, interesting job both in our country and abroad.

Still in demand: profession "".

Rust is a new experimental programming language being developed by Mozilla. The language is compiled and multi-paradigm, positioned as an alternative to C/C++, which in itself is interesting, since there are not even that many contenders for competition. You can remember Walter Bright's D or Google's Go.
Rust supports functional, parallel, procedural and object-oriented programming, i.e. almost the entire range of paradigms actually used in application programming.

My goal is not to translate the documentation (besides, it is very scarce and is constantly changing, since there has been no official release of the language yet), instead I want to highlight the most interesting features of the language. The information was collected both from official documentation and from the extremely few mentions of the language on the Internet.

First impression

The syntax of the language is built in a traditional C-like style (which is good news, since this is already a de facto standard). Naturally, the well-known C/C++ design errors have been taken into account.
A traditional Hello World looks like this:
use std; fn main(args: ) ( std::io::println("hello world from " + args + "!"); )

A slightly more complicated example - the factorial calculation function:

Fn fac(n: int) -> int ( let result = 1, i = 1; while i<= n { result *= i; i += 1; } ret result; }

As you can see from the example, functions are declared in the “functional” style (this style has some advantages over the traditional “int fac(int n)”). We see automatic type inference(let keyword), no parentheses in the while argument (same as Go). Another thing that immediately catches your eye is the compactness of the keywords. The creators of Rust really made a point of keeping all the keywords as short as possible, and to be honest, I like it.

Small but interesting syntactic features

  • You can insert underscores into numeric constants. This is a handy feature; this feature is now being added to many new languages.
    0xffff_ffff_ffff_ffff_ffff_ffff
  • Binary constants. Of course, a real programmer must convert bin to hex in his head, but it’s more convenient this way! 0b1111_1111_1001_0000
  • The bodies of any statements (even those consisting of a single expression) must be enclosed in curly braces. For example, in C you could write if(x>0) foo(); , in Rust you must put curly braces around foo()
  • But the arguments of if, while and similar operators do not need to be enclosed in parentheses
  • in many cases, blocks of code can be thought of as expressions. In particular, the following is possible:
    let x = if the_stars_align() ( 4 ) else if something_else() ( 3 ) else ( 0 );
  • function declaration syntax - first the fn keyword, then a list of arguments, the type of the argument is indicated after the name, then, if the function returns a value, an arrow "->" and the type of the return value
  • Variables are declared in the same way: the keyword let, the name of the variable, after the variable you can specify the type through a colon, and then assign an initial value.
    let count: int = 5;
  • By default, all variables are immutable; The mutable keyword is used to declare mutable variables.
  • the names of the base types are the most compact of all that I have come across: i8, i16, i32, i64, u8, u16, u32, u64,f32, f64
  • as mentioned above, automatic type inference is supported
The language has built-in program debugging tools:
Keyword fail ends the current process
Keyword log outputs any language expression to the log (for example, to stderr)
Keyword assert checks the expression and if it is false, terminates the current process
Keyword note allows you to display additional information in case of abnormal termination of the process.

Data types

Rust, like Go, supports structural typing(although, according to the authors, the languages ​​​​developed independently, so this is the influence of their common predecessors - Alef, Limbo, etc.). What is structural typing? For example, you have a structure declared in some file (or, in Rust terminology, a “record”)
type point = (x: float, y: float);
You can declare a bunch of variables and functions with "point" argument types. Then, somewhere else, you can declare some other structure, like
type MySuperPoint = (x: float, y: float);
and variables of this type will be fully compatible with variables of type point.

In contrast, the nominative typing adopted in C, C++, C# and Java does not allow such constructs. With nominative typing, each structure is a unique type, incompatible by default with other types.

Structures in Rust are called “records”. There are also tuples - these are the same records, but with nameless fields. Elements of a tuple, unlike elements of a record, cannot be mutable.

There are vectors - in some ways similar to ordinary arrays, and in some ways similar to the std::vector type from stl. When initializing with a list, square brackets are used, not curly brackets as in C/C++

Let myvec = ;

A vector, however, is a dynamic data structure; in particular, vectors support concatenation.

Let v: mutable = ; v += ;

There are templates. Their syntax is quite logical, without clutter of “template” from C++. Function and data type templates are supported.

Fn for_rev (v: [T], act: block(T)) ( let i = std::vec::len(v); while i > 0u ( i -= 1u; act(v[i]); ) ) type circular_buf = (start: uint, end: uint, buf: );

The language supports so-called tags. This is nothing more than a union from C, with an additional field - the code of the variant used (that is, something in common between a union and an enumeration). Or, from a theoretical point of view, an algebraic data type.

Tag shape ( circle(point, float); rectangle(point, point); )

In the simplest case, a tag is identical to an enumeration:

Tag animal ( dog; cat; ) let a: animal = dog; a = cat;
In more complex cases, each element of the “enumeration” is an independent structure that has its own “constructor”.
Another interesting example is a recursive structure that is used to define an object of the “list” type:
tag list ( nil; cons(T, @list ); ) let a: list = cons(10, @cons(12, @nil));
Tags can participate in pattern matching expressions, which can be quite complex.
alt x ( cons(a, @cons(b, _)) ( process_pair(a,b); ) cons(10, _) ( process_ten(); ) _ ( fail; ) )

Pattern matching

To begin with, we can consider the matching pattern as an improved switch. The alt keyword is used, followed by the expression being analyzed, and then in the body of the statement - patterns and actions if the patterns match.
alt my_number ( 0 ( std::io::println("zero"); ) 1 | 2 ( std::io::println("one or two"); ) 3 to 10 ( std::io::println ("three to ten"); ) _ ( std::io::println("something else"); ) )
As “patterns” you can use not only constants (as in C), but also more complex expressions - variables, tuples, ranges, types, placeholders ("_"). You can specify additional conditions using the when statement immediately following the pattern. There is a special variant of the operator for type matching. This is possible because the language has a universal variant type any, whose objects can contain values ​​of any type.

Pointers. In addition to the usual “C” pointers, Rust supports special “smart” pointers with built-in reference counting - shared (Shared boxes) and unique (Unique boxes). They are somewhat similar to shared_ptr and unique_ptr from C++. They have their own syntax: @ for shared and ~ for unique. For unique pointers, instead of copying, there is a special operation - moving:
let x = ~10; let y<- x;
After such a move, the x pointer is deinitialized.

Closures, partial applications, iterators

This is where functional programming begins. Rust fully supports the concept of higher-order functions—that is, functions that can take other functions as arguments and return them.

1. Keyword lambda used to declare a nested function or functional data type.

Fn make_plus_function(x: int) -> lambda(int) -> int ( lambda(y: int) -> int ( x + y ) ) let plus_two = make_plus_function(2); assert plus_two(3) == 5;

In this example, we have a function make_plus_function that takes one argument "x" of type int and returns a function of type "int->int" (lambda is the keyword here). The function body describes this very function. The lack of a “return” operator is a little confusing, but this is a common thing for FPs.

2. Keyword block used to declare a functional type - a function argument, which can be replaced by something similar to a block of regular code.
fn map_int(f: block(int) -> int, vec: ) -> ( let result = ; for i in vec ( result += ; ) ret result; ) map_int((|x| x + 1 ), );

Here we have a function whose input is a block - essentially a lambda function of type “int->int”, and a vector of type int (more on the syntax of vectors later). The “block” itself is written in the calling code using a somewhat unusual syntax (|x| x + 1). Personally, I prefer lambdas in C#, the symbol | persistently perceived as bitwise OR (which, by the way, is also present in Rust, like all the old C-based operations).

3. Partial application is the creation of a function based on another function with more arguments by specifying the values ​​of some of the arguments of that other function. The keyword used for this is bind and a placeholder character "_":

Let daynum = bind std::vec::position(_, ["mo", "tu", "we", "do", "fr", "sa", "su"])

To make it clearer, I’ll say right away that this can be done in regular C by creating a simple wrapper, something like this:
const char* daynum (int i) ( const char *s =("mo", "tu", "we", "do", "fr", "sa", "su"); return s[i]; )

But partial application is a functional style, not procedural (by the way, from the example given it is not clear how to do a partial application to get a function without arguments)

Another example: the add function is declared with two int arguments, returning int. Next, the functional type single_param_fn is declared, which has one argument int and returns int. Using bind, two function objects add4 and add5 are declared, built on the basis of the add function, which has partially specified arguments.

Fn add(x: int, y: int) -> int ( ret x + y; ) type single_param_fn = fn(int) -> int; let add4: single_param_fn = bind add(4, _); let add5: single_param_fn = bind add(_, 5);

Function objects can be called in the same way as regular functions.
assert (add(4,5) == add4(5)); assert (add(4,5) == add5(4));

4. Pure functions and predicates
Pure functions are functions that do not have side effects (including those that do not call any other functions except pure ones). Such functions are identified by the pure keyword.
pure fn lt_42(x: int) -> bool ( ret (x< 42); }
Predicates are pure functions that return type bool. Such functions can be used in the typestate system (see below), that is, called at the compilation stage for various static checks.

Syntactic macros
A planned feature, but very useful. It's still in early development in Rust.
std::io::println(#fmt("%s is %d", "the answer", 42));
An expression similar to C's printf, but executed at compile time (accordingly, all argument errors are detected at the compilation stage). Unfortunately, there are very few materials on syntactic macros, and they themselves are under development, but there is hope that something like Nemerle macros will turn out.
By the way, unlike Nemerle, I consider the decision to highlight macros syntactically using the # symbol to be very smart: a macro is an entity that is very different from a function, and I think it is important to see at first glance where in the code functions are called and where - macros.

Attributes

A concept similar to C# attributes (and even with similar syntax). Special thanks to the developers for this. As you would expect, attributes add meta information to the entity they annotate.
# fn register_win_service() ( /* ... */ )
Another variant of attribute syntax has been invented - the same line, but with a semicolon at the end, annotates the current context. That is, what matches the nearest curly braces enclosing such an attribute.
fn register_win_service() ( #; /* ... */ )

Parallel Computing

Perhaps one of the most interesting parts of the language. At the same time, it is not described at all in the tutorial at the moment :)
A Rust program consists of a “task tree”. Each task has an input function, its own stack, means of interaction with other tasks - channels for outgoing information and ports for incoming information, and owns some of the objects in the dynamic heap.
Multiple Rust tasks can exist within a single operating system process. Rust tasks are “lightweight”: each task consumes less memory than the OS process, and switching between them is faster than switching between OS processes (here, probably, we mean “threads”).

A task consists of at least one function with no arguments. The task is launched using the spawn function. Each task can have channels through which it transmits information to other tasks. A channel is a special template type chan, parameterized by the channel data type. For example, chan is a channel for transmitting unsigned bytes.
To send to a channel, use the send function, the first argument of which is the channel, and the second is the value to be sent. This function actually places the value into the internal buffer of the channel.
Ports are used to receive data. A port is a generic port type, parameterized by a port data type: port is a port for receiving unsigned bytes.
To read from ports, use the recv function, whose argument is the port and the return value is the data from the port. Reading blocks the task, i.e. if the port is empty, the task enters the waiting state until another task sends data to the channel associated with the port.
Associating channels with ports is very simple - by initializing the channel with a port using the chan keyword:
let reqport = port();
let reqchan = chan(reqport);
Several channels can be connected to one port, but not vice versa - one channel cannot be connected to several ports at the same time.

Typestate

I have not found a generally accepted translation into Russian of the concept “typestate”, so I will call it “type states”. The essence of this feature is that in addition to the usual type checking adopted in static typing, additional contextual checks are possible at the compilation stage.
In one form or another, type states are familiar to all programmers - according to the compiler messages, “the variable is used without initialization.” The compiler detects places where a variable that has never been written to is being read and issues a warning. In a more general form, this idea looks like this: every object has a set of states that it can take. Each state defines valid and invalid operations for that object. And the compiler can check whether a specific operation on an object is allowed in a particular place in the program. It is important that these checks are performed at compilation time.

For example, if we have an object of type “file”, then it can have a state of “closed” and “open”. And a read operation from a file is not allowed if the file is closed. In modern languages, it is common for the read function to either throw an exception or return an error code. A type state system could detect such an error at compile time - just as the compiler determines that a read operation on a variable occurs before any possible write operation, it could determine that the "Read" method, valid in the "file open" state, is called before the “Open” method, which transfers the object to this state.

Rust has the concept of “predicates” - special functions that have no side effects and return a bool type. Such functions can be used by the compiler to be called at the compilation stage for the purpose of static checks of certain conditions.

Constraints are special checks that can be performed at compile time. The check keyword is used for this.
pure fn is_less_than(int a, int b) -< bool { ret a < b; } fn test() { let x: int = 10; let y: int = 20; check is_less_than(x,y); }
Predicates can be “hung” on the input parameters of functions in this way:
fn test(int x, int y) : is_less_than(x,y) ( ... )

There is very little information on typestate, so many aspects are still unclear, but the concept is interesting anyway.

That's all. It is quite possible that I still missed some interesting points, but the article was already bloated. If you wish, you can now build a Rust compiler and try to play with various examples. Assembly information is provided at


We really liked the article "Criticism of the Rust language and why C/C++ will never die." We suggested to the author that we would translate the article into English and also publish it on our blog. He agreed, and we are pleased to present this article in Russian and English. The original article is located.

The original article is posted (text in Russian). The article was published on our blog with the agreement of the author.

Note: In the following, I am making the assumption that Rust is an attempt to make a fast and safe language. After all, the Mozilla guys made it as a browser engine development tool. If this is just another safe language, then we get something strange. There are already a dime a dozen different safe languages, everyone will find something to their liking. And if the goal is not to replace C++, then (1) why is an unsafe subset made in the language? (2) why was it necessary to remove lightweight streams from the language? Isn’t it convenient? In other words, in this case, what is happening makes no sense at all.

If you happen to be reading the linux.org.ru forum, I’ll note that this is not the list of 10 purely technical reasons not to like Rust that was discussed in this thread. As shown by a discussion on Skype with dear comrade @sum3rman, there is more than one opinion regarding how “technical” these reasons should be considered. In general, I made a crappy list, but I’ll probably still risk citing some of the most interesting points from it. In fact, there are plenty of simple, non-technical reasons here.

The fact that C/C++ is not going anywhere in the foreseeable future is clear to any sober-minded person. No one will rewrite almost all desktop applications, operating system kernels, compilers, game and browser engines, virtual machines, databases, archivers, audio and video codecs, tons of other C-based libraries, and so on. This is a lot, a lot of fast, debugged, time-tested code. Rewriting it is very, very expensive, risky, and to be honest, it makes sense only in the distorted consciousness of only the most stubborn Rust fans. The demand for C/C++ programmers has been and will be great for a very long time.

Okay, what about using Rust when writing new code?

Let us remember that this is not the first attempt to make a “more correct” C/C++. Let's take, for example, language D. It appeared in 2001, a very good language. There are no vacancies, no normal development tools, no particularly outstanding success stories. The OpenMW project was originally written in D, and then suddenly they decided to rewrite it entirely in C++. As the developers admit, they received many letters in the style of “great project, we would be happy to contribute to it, but we don’t know and don’t want to know this stupid D.” Wikipedia reports that in addition to D, there were a lot of other attempts to kill C++ to one degree or another, for example, Vala, Cyclone, Limbo, BitC. How many people have even heard of such languages?

I think it's high time we learned from history. Not a single sane person will drag a new language into a project until you at least show him normal development tools, tell him a couple of success stories and show him a dozen programmers in this language living nearby. Programmers, perhaps, except for the youngest ones, will never waste their time and health on learning the next most correct language until you show them normal development tools (not crafts like Racer), a couple of tens of thousands of ready-made libraries (not “experimental”, “unstable” and so on), don’t tell a couple of success stories and show a dozen open vacancies in their city. Chicken and egg problem. Very rarely, this problem can be successfully solved (conditionally, Scala can be cited as an example), mainly due to the investment of time and money on the part of some large company (Google, Typesafe), for some reason interested in popularizing the language.

As I already noted, non-technical reasons alone are more than enough. However, purely out of curiosity, let's try to imagine for a second that they are not there. Then there's no reason not to write in Rust? It turns out that this is also at least a very big question.

C/C++ is criticized for various things. By the way, criticism is very often made by those who have not even seen C++ code in production even from a distance. The problem can be briefly and clearly described as follows: C++ is very fast (and also not demanding on memory, battery charge, etc.), but not safe in the sense that it allows you to go beyond the boundaries of arrays, mistakenly access freed pieces of memory, and so on Further. At one time, this problem led to the emergence of a mass of safe languages, such as Java, C#, Python and others. But it turned out that these languages, compared to C++, are too resource-demanding and have other disadvantages, for example, the inevitable stop the world during garbage collection. Therefore, people are struggling with the task of making a language as fast as C++, but also secure. One such language is Rust.

Rust is indeed secure, but unfortunately, it is far from fast. At the time of writing, Rust is comparable in speed to Java, Go and Haskell:

I sincerely hope that over time it will somehow be overclocked, but until then, in terms of speed and security trade-offs, it is not much more interesting than Scala or Go. The question still remains open whether it is possible to make a language fast and safe at all, or whether constant checks for exceeding array boundaries, safe bindings around bindings to C-libraries, and so on automatically make any language 2 times slower than C/C++.

What exactly makes Rust safe? In simple terms, it is a language with a built-in static code analyzer. A really very cool static analyzer that catches all typical C++ errors, not only those related to memory management, but also multithreading. I passed a link to a mutable object to another thread via a channel, and then tried to use this link myself - it didn’t compile. It's really cool.

The argument is often made that only 10% of the code is executed 90% of the time (which, as far as I understand, is purely a rule of thumb - I couldn't quickly find any rigorous research on this topic). Therefore, most of the program can be written in safe Rust, with 10% of the hot code written in the unsafe subset, and the slowness of the current Rust implementation is not really a problem. Ok, but then it turns out that Rust is not needed at all, because I can write 90% of the code in Go, and 10% in C. Only silver bullet seekers and out-of-touch theorists will use Rust solely for the reason that 100% of the program can be written in one language. Although in reality these are two dialects of the same language, which is not so different from the combination of Java plus C or Go plus C.

In fact, the 10:90 rule is still a lie. By this logic, you could rewrite 90% of WebKit, 90% of VirtualBox or 90% of GCC in Java and get the same result. Obviously this is not the case. Even if the point is not that in a number of programs this attitude is very different, then watch your hands. Let's say the entire program is written in unsafe C/C++ and its execution time, relatively speaking, is equal to 0.9*1 (a small part of hot code) + 0.1*1 (a lot of cold code) = 1. Now let's compare it with a program in a safe language with inserts in Si: 0.9*1 + 0.1*2 = 1.1, roughly 10% of the difference. Is this a lot or a little? Depends on your scale. In the case of Google, even a few percent can save millions of dollars (see point 5 in the paper, “Utilization”). Or imagine that with the next update the JVM suddenly starts requiring 10% more resources! I'm afraid to even guess how many zeros there will be in the figure obtained after converting interest to American money! 10% is a lot in tasks where C and C++ are used.

We repeat “premature optimization is the root of all evil” like a mantra. But if we take it literally, let's use bubble sort instead of quicksort everywhere. We don’t know for sure that the program will slow down in this particular place! What's the point of wrapping ordinary counters of some actions in actors or transactional memory if you can immediately use the more efficient atomic? And in general, in trivial cases there is no point in forcibly initializing all, all, all variables, doing a bunch of additional checks, and so on. Let us end up with not 10% acceleration, but 2-5%. This is also not bad at all, if it only required a couple of extra minutes of thought. And as we have already found out, in problems solved in C/C++, this can be a big difference! Then, who said that finding a hot spot, rewriting the code (possibly a lot of code) and proving that it is really faster is easier than thinking about performance in advance?

If we ignore the issue of the speed-security trade-off, I also have questions about the design of the language itself. In particular, regarding the five types of pointers. On the one hand, this is not bad when the programmer thinks about where the variables are located, on the stack or heap, and whether several threads can or cannot work with them simultaneously. But on the other hand, imagine that you are writing a program, and it turns out that the variable should live not on the stack, but on the heap. You rewrite everything to use Box. So you understand that what you really need is Rc or Arc. You rewrite again. And then you rewrite it again to a regular variable on the stack. All this - without a normal IDE at hand. And regular games won't help. Well, or just in the style of "Vec" >>>", hello, Java! But the saddest thing is that the compiler already knows about the lifetime of all variables, it could output all these Box, Arc and so on automatically. But for some reason this part of the work is transferred to the programmer. Much more convenient it would be easy to write val (in the third millennium!), and where necessary, explicitly indicate Box or Rc. The Rust developers in this sense ruined the whole idea.

Because of this, in particular, the scope of application of Rust is greatly narrowed. Nobody in their right mind would write web and serverside in such a language. Especially considering that it does not provide significant advantages over the same languages ​​under the JVM. And Go with normal lightweight threads (not futurs) looks much more attractive for these tasks. With futurs, in order not to shoot yourself in the foot, you still need to learn how to work, and you say “safe language”. Yes, these languages ​​have their own characteristics, take the same stop the world, but this problem can be solved both by cutting into microservices and by other techniques. And yes, no one will translate Rust into JavaScript, write scripts in it for layout in AWS, or use it as a query language for MongoDB. It is also unlikely that they will write for Android, but for a different reason - there is much more than one architecture, and it is much easier with the JVM. If you suddenly thought that Rust was “suitable for all tasks,” I have to disappoint you.

Well, to the heap:

  • Macros are a backup to excessive verbosity caused by the lack of normal exceptions. I have already written about the problems of metaprogramming, in particular, we are unlikely to see a normal IDE for Rust because of it. And I'm not sure, but it looks like macros in Rust don't even have namespaces.
  • People are idiots, and cargo really encourages pulling packages directly from git repositories, bypassing Crates.io. As a result, there is a high probability of getting the same mess with packages as in the world of Erlang with its Rabar. By the way, in the world of Go, it seems to be the same situation.
  • Like many new languages, Rust takes the path of simplification. In general, I understand why there is no normal inheritance and exceptions in it, but the very fact that someone decides such things for me leaves an unpleasant aftertaste. C++ does not limit the programmer in terms of what to use and what not to use.
  • If we were to follow the path of simplification, then we would have to throw out all these language extensions. Otherwise, it turns out, as in the world of Haskell, each programmer writes in his own dialect.
  • Smart pointers, if anything, are far from free and do not lead to predictable garbage collection times. Some thread suddenly has the honor of freeing a very deep data structure. While he walks through the labyrinth of dead links, the threads that depend on him patiently become stupid. The same problem exists in Erlang with its small groups, I have observed it myself more than once. Smart pointers also have their own problems, the same memory fragmentation and leaks. I forgot the vikpointer in the cyclic structure, that's all. And this in a language that claims to be safe. If you want predictable GC time, either study the behavior of your application under load, and take action (remember the same object pools) if the GC time does not suit you, or manage memory manually.
  • Has anyone seen a rigorous description of Rust semantics? Does it at least have a memory model? Also to me a “safe” language that “proves the correctness” of programs, which can actually interpret the source code in ten different ways, ha!
  • I cannot help but remind you once again that The problem is almost always people, not technology.. If you end up with bad C++ code or Java suddenly slows down, it's not because the technology is bad, but because you haven't learned how to use it correctly. You will also be unhappy with Rust, but for different reasons. Wouldn't it be easier to learn to use and love more popular tools?

In general, over the next 5 years I would rather invest my time in learning C/C++ than Rust. C++ - this is an industry standard. A wide variety of problems have been successfully solved in this language for more than 30 years. And Rust and others like it are incomprehensible toys with a vague future. There have been conversations about the imminent death of C++ since at least the 2000s, but writing in C/C++ has begun no less during this time. Quite the contrary. And we see that the language is developing (C++11, C++14), new tools are appearing for it (let’s remember CLion and Clang), and there are simply a lot of corresponding vacancies.

A C++ programmer can always easily find a job with a more than decent salary, and if necessary, quickly retrain in Rust. The opposite is very, very doubtful. By the way, language, if anything, is far from the only and not the decisive factor when choosing a new place of work. In addition, an experienced C/C++ programmer can easily dig into the PostgreSQL source code or Linux kernel, use powerful modern development tools, and also have a lot of books and articles at his disposal (say, on OpenGL).

Take care of your time and health, you don’t have as much of it as you think!



Hello, dear readers!

Life does not stand still, and O"Reilly thought about publishing the first fundamental book about the Rust programming language:

Having become interested in this topic, we decided to bring up for discussion the translation of a review article about the Rust language, published in December 2014. The article has been slightly shortened due to the fact that some of its passages are already outdated, but the author takes a good look at this language in the context of existing alternatives, emphasizing its (unconditional) advantages and (conditional) disadvantages.

However, to make it even more interesting, let’s leave in the comments to this article a link to another article about Rust, published in one of our favorite Russian-language programming blogs. To begin with, go to cat.

Disclaimer: Taste for programming languages ​​is a very subjective matter, just like this post. Take it with healthy skepticism.

Several new programming languages ​​have emerged recently. Among them, I was especially interested in Rust. Below I'll share my impressions of Rust and compare it to several other programming languages.

Barrier to learning Rust

I didn't get to know Rust on my first try. There are several barriers to learning this language, including:

  1. Language is changing rapidly. There is no “benign dictator for life” in Rust. The language evolves through the contributions of core team members and the community.
  2. Considering the first point, Rust tutorials are very sparse. There's a manual, other official documentation, and the Rust by Example site are great resources. However, Rust is much more complex. Often you have to scour RFCs, blogs, and even comments on Github to find the information you need, and even if this information appeared just yesterday, you are still not completely sure of it. I'm looking forward to a good, authoritative book on Rust, although I bet it will be lengthy.
  3. Rust's ownership system and borrowing check mechanism can be confusing for newbies. To ensure memory safety without garbage collection, Rust uses an intricate system of borrowing and ownership. She often scares away neophytes.
  4. The Rust compiler is very strict. I call Rust a discipline language. Anything that is not completely obvious to the Rust compiler must be specified yourself, and some of your intentions you yourself may not even be aware of at first. This learning barrier, along with all the others, often results in a discouraging first impression of Rust.

Advantages

Rust has many advantages. Some of them are unique.

Memory safety without garbage collection

This is perhaps Rust's most important achievement. In low-level programming languages ​​that allow direct memory manipulation, errors such as use-after-free or memory leaks at runtime are quite expensive. In modern C++, the ability to deal with such things has improved, but it requires strict technical discipline (read: programmers continue to perform unsafe operations). Accordingly, in my opinion, in general, C++ cannot fundamentally and reliably solve this problem.

It is true that Rust programmers can write unsafe code in an unsafe block, but (1) this is done deliberately and (2) unsafe blocks may only constitute a very small portion of the entire code base, but they are strictly controlled.
The garbage collector is the most common memory safety tool. If you get along with GC, then you have quite a few options. However, Rust's ownership system ensures not only memory security, but also data and resource security (see below)

RAII and resources

RAII (resource acquisition is initialization) is a strange term, but it conveys the idea well. On Wikipedia we read that RAII works with objects allocated on the stack. Rust's ownership system allows this principle to be applied to heap-allocated objects as well. This makes the automatic release of resources - for example, memory, files, sockets - highly predictable and guaranteed at compile time.
Dynamic languages ​​like Python or Ruby have similar capabilities, but they don't match the power of Rust IMO.

Competitiveness without data races

Rust provides data security for concurrent programming - that is, it ensures that only many readers or one "writer" can access the data at any given time.

Algebraic data type

In addition to the regular types (tuples and structures), Rust also provides enumeration types (here called “sum types” or “variant types”) and pattern matching. It's surprising that a systems programming language has such a developed type system.

Composition over inheritance

Rust clearly favors type composition over inheritance. I'm in the camp where this fact is considered a win. When Rust supports generic types, traits play a key role.

Disadvantages (conditional)

Everything must be very clear

Rust is a disciplined language. The compiler needs to communicate everything very clearly, or it will swear until there are no unclear points left. This generally benefits code quality, but can be overkill when it comes to rapid prototyping or one-off tasks.

As a result: you have to write better and clearer code in Rust. Once you understand this, the rough edges can more or less disappear.

Garbage collection is secondary

Rust has a very basic garbage collector: Rc, a reference counting, and Arc, an atomic reference counting without round robin detection. However, these features don't work in the language by default, and you'll have to use Rust's standard memory management mechanisms (Stack, &, and Box) more often. If memory problems in your application are not significant, then you will have to tolerate Rust's memory safety model, which does not use garbage collection.

Expressiveness is not an end in itself

The Rust language doesn't worry about expressiveness or beauty of code. It's definitely not bad in that regard, but it's not as wonderful as you might want it to be.

Relatively high barrier to entry

In principle, Rust is not one of those languages ​​that you can quickly master and write professional code in a few weeks. Rust is perhaps more compact than C++, but it is definitely larger than many programming languages. Compared to other languages, it cannot be called very accessible. This can be a problem if your priority is language acquisition speed.

Rust and other languages

Dynamic languages

Dynamic (scripting) languages ​​are at the opposite end of the programming language spectrum from Rust. Compared to Rust, writing code in dynamic languages ​​is usually faster and easier. I think dynamic languages ​​beat Rust in these situations:

  • Rapid prototyping or one-off tasks
  • The code is not for production, or one where an error at runtime is a small problem
  • Own (individual) project
  • Semi-automatic work (e.g. parsing/analysis of logs, batch text processing)

In such cases, you should not try to do everything perfectly. On the contrary, Rust, in my opinion, is better suited for:

  • Work in a medium or large team
  • Code oriented for long-term use in production
  • Code that will be used for a long time requires regular maintenance and/or refactoring
  • Code that you would write a lot of unit tests to ensure safety

In general, when code quality is critical. Dynamic languages ​​help you write code faster at the initial stage, but later the work slows down: you have to write more tests, the development line is disrupted, or even interruptions occur in production. The Rust compiler forces you to do many things correctly at compile time, when it is less expensive to identify and fix bugs.

Go

Comparing these two languages ​​is an excellent reason to argue, but since I have been studying for some time, I will still share here my subjective impressions of it. Compared to Rust, here's what I like about Go:

  • lightweight - the language is small (and simple, but very powerful)
  • gofmt utility – significantly reduces the mental load when programming
  • goroutine/channel
  • Instant compilation

Why I quit Go:

  • It's too minimalistic. The type system and the language itself are not very extensible
  • Go programming seems a bit dry to me. Reminds me of the days when I programmed in Java: good for enterprise development, mechanical and... not so interesting (reminder: there is no dispute about tastes)
  • Go's popularity continues thanks to Google's support, but that makes me a little skeptical. When the interests of the community and the company do not coincide, the first may be sacrificed. Of course, any company primarily pursues its own interests. There is nothing wrong. It's just... a little annoying. (Many languages ​​and frameworks promoted by corporations face a similar problem. At least Mozilla does not depend on stock prices.)

Nim

Nim (formerly called Nimrod) is a very interesting language. It compiles to C, so performance is quite good. Outwardly, it resembles Python, a language in which I have always liked programming. It is a garbage-collected language, but it provides soft real-time support and the behavior of the garbage collector itself is more predictable. It has an interesting effects system. In principle, I really like this language.

The biggest problem in his case is the immaturity of the ecosystem. The language itself is well-built and relatively stable, but currently this is nowhere near enough for a programming language to succeed. Documentation, standard libraries, package repositories, supporting frameworks, community and third-party participation... getting it all ready for production is not easy.

Without specialists to complete the language in full time, this last stage can be very grueling. Among new programming languages, Nim cannot yet boast of serious support.
That being said, I wish him success and continue to follow him.

Others

There are other languages ​​like Julia and . Julia is a dynamic language with good performance and smooth C-style calls. (If you like dynamic languages ​​and REPLs, pay attention). Julia has gained everyone's attention thanks to its numeric and scientific fields. While it has the potential to become a general-purpose language, it seems to me that the development of a language is greatly influenced by the community of its originators.

D, at least initially, was an attempt to create "C++, but better." Its version 1.0 was released in 2007, so this language is not that new. This is a good language, but for objective reasons it has not yet taken root: the reason is the split into Phobos/Tango at an early stage, and the provision of memory safety primarily through garbage collection, and the initial positioning as a replacement for C++.

Why I think Rust's chances are pretty high

There are so many new programming languages ​​coming out these days. What, in my opinion, makes Rust stand out among them? I will give the following arguments:

A real language for systems programming

Embedding is not an easy task. Perhaps it can be solved in literally several languages, or even just two: C and C++. (This may be why Skylight chose Rust to develop an extension for Ruby, even though it was extremely risky.) It's remarkable how well Rust has managed to eliminate runtime overhead. This opens up a unique perspective for Rust.

Without Null

Null object/pointer (the so-called "billion dollar bug") is a common source of runtime errors. There are only a few programming languages ​​that do not have null, mostly functional languages. The point is that getting rid of null requires a very advanced type system. Typically, dealing with this at the syntactic level of the language requires an algebraic data type and pattern matching.

Low-level language with advanced high-level constructs

Being a "bare metal language" down to the core (at least in theory), Rust also offers many relatively high-level features, including algebraic data type, pattern matching, trait, type inference, etc.

Strong community and practical relevance

The Rust community is very friendly and active. (Of course, this is a subjective impression). In addition, Rust has been used in some serious practical projects - in particular, the Rust compiler, Servo, Skylight, etc. still at the stage of language development.

So far - no major errors

At times, the development of a language or framework carried out within a company can accidentally reach a dead end. Luckily, the core Rust team is doing a great job so far. Keep it up, Rust!

Rust for web development

If Rust is a systems programming language, is it suitable for web development? I'm looking for an answer to this question as well.

Libraries and frameworks

First of all, some HTTP libraries must be ready for this. (This is described on the “Are we web yet” website). The first rust-http library is already outdated; her potential successor Teepee is practically in suspended animation. Luckily, Hyper seems like a good candidate. It has already been accepted into Servo, a symbiotic project of Rust, which I consider a blessing to be the HTTP library for Rust.

The Rust standard library does not yet support asynchronous I/O. For this purpose, you can use the external mio library, which provides non-blocking socket I/O. Green thread support has been removed as part of I/O simplification.

Several web frameworks for Rust are actively being developed, including Iron and nickel.rs. It may take time before the situation with them calms down.

Is Rust a language for the web?

Someday the libraries and frameworks will be ready. The question is, is Rust itself suitable for web development? Are Rust's low-level memory management and security features too complex?

I think in the end it all depends on what you expect from the project. Above, when comparing Rust to dynamic languages ​​in short-term projects, I mentioned that in such cases, the complexity of Rust can be unjustified. But if you expect the product to last a long time - say, six months or more - then Rust might be a good option.

Is Rust good for web startups?

What about startups? They require rapid prototyping and development cycles. This is a more controversial question, but I stand by my opinion: if you are looking at a long-term project, then choosing the right programming language is important, and Rust deserves special attention. From a business perspective, a language that enables rapid prototyping provides significant benefits, while refactoring and eliminating bottlenecks can always be left for later. The engineering reality is that the cost of refactoring work is usually higher than it seems, and even if you shake up a lot of elements of your system, the code written a long time ago will still remain in some corner. For many years.

Try Rust!

You can help and transfer some funds for the development of the site



Best articles on the topic