Internet Windows Android

Rust: getting started with a new programming language from Mozilla specialists. The Rust language and why you should eat it Rust where to start programming


We really liked the article "Criticizing the Rust Language and Why C / C ++ Will Never Die". We suggested to the author that we would translate the article into English and 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: I am assuming below that Rust is an attempt to make a fast and secure language. After all, the guys at Mozilla made it as a browser engine development tool. If this is just another safe language, then we get strange. There are already a dime a dozen of different safe languages, everyone will find to their liking. And if the goal is not to replace C ++, then (1) why is the unsafe subset made in the language? (2) why was it convenient to remove lightweight streams from the language? In other words, in this case, what is happening does not make any sense at all.

If you suddenly read the linux.org.ru forum, I note that this is not the list of 10 purely technical reasons not to like Rust, which was discussed in this thread. As the Skype discussion with dear comrade @ sum3rman There is more than one opinion on how "technical" these reasons are. In general, I made a fig list, but some of the points from it, the most interesting, perhaps, I still venture to cite. In fact, there are enough simple, non-technical reasons for the eyes.

The fact that C / C ++ will not go anywhere in the foreseeable future is clear to any sober-minded person. Nobody is going to rewrite almost all desktop applications, operating system kernels, compilers, game and browser engines, virtual machines, databases, archivers, audio and video codecs, tons of other sish libraries, and so on. This is 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 minds of only the most stubborn Rust "Oman. The demand for C / C ++ programmers has been and will be great for a very long time.

Okay, how about using Rust when writing new code?

Let's remember that this is not the first attempt to make "more correct" C / C ++. Let's take the D language. It appeared in 2001, a very good language. There are no vacancies, no normal development tools, or any 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 a lot of letters in the style of "great project, we would be happy to contribute to it, but we do not know and do not 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 to learn 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 a couple of success stories and show a dozen programmers in this language living nearby. Programmers, perhaps, except for the youngest, 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. The chicken and egg problem. Very rarely, this problem can be successfully solved (for example, Scala can be cited here as an example), mainly due to the investment of time and money on the part of some large company (Google, Typesafe), which, for some reason, is interested in popularizing the language.

As I 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 ++ gets criticized for different things. By the way, it is very often criticized by those who have not even seen the C ++ code in production from a distance. Briefly and clearly, the problem can be described as follows: C ++ is very fast (and also not demanding on memory, battery power, etc.), but not safe in the sense that it allows you to go beyond the boundaries of arrays, mistakenly access the 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, in comparison with C ++, are too resource-demanding and have other drawbacks, remember at least the inevitable stop the world during garbage collection. Therefore, people are struggling to make the language as fast as C ++, but also safe. One of these languages ​​is Rust.

Rust is indeed secure, but unfortunately far from fast. In terms of speed at the time of this writing, Rust is comparable to Java, Go and Haskell:

I sincerely hope that over time it will be somehow overclocked, but until then, in terms of the compromise of speed and security, it is not much more interesting than Scala or Go. Until now, the question remains whether it is possible to make a language fast and safe at all, or constant checks for out of bounds of an array, safe binding around bindings to C libraries, and so on automatically make any language 2 times slower than C / C ++.

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

It is often argued that only 10% of the code is executed 90% of the time (which, as I understand it, is a purely rule of thumb - no rigorous research has been found quickly on this topic). Therefore, most of the program can be written in safe Rust, and 10% of the hot code can be written in an 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 seekers of silver bullets and the heretics divorced from reality will use Rust solely on the grounds that 100% of the program can be written in one language, as it were. Although in reality these are two dialects of the same language, which is not that much different from a bunch of Java plus C or Go plus C.

In fact, the 10:90 rule is still a lie. Following this logic, you can rewrite 90% of WebKit, 90% of VirtualBox or 90% of GCC in Java and get the same result. This is obviously 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 whole program is written in unsafe C / C ++ and its execution time, relatively speaking, is 0.9 * 1 (a small part of the hot code) + 0.1 * 1 (a lot of cold code) = 1. Now let's compare with a program in a safe language with insertions in Si: 0.9 * 1 + 0.1 * 2 = 1.1, conventionally 10% of the difference. Is it a lot or a little? Depends on your scale. In the case of Google, even a few percent can save millions of dollars (see paragraph 5 in the paper, "Utilization"). Or imagine that with the next update, the JVM suddenly starts to require 10% more resources! I'm afraid to even guess how many zeros there will be in the figure obtained after the transfer of interest to American money! 10% is dofig in tasks where C and C ++ are used.

We repeat "premature optimization is the root of all evil" as a mantra. But literally, let's use bubble sort everywhere instead of quicksort. We do not know for sure that the program will slow down exactly in this place! What is the point of wrapping ordinary counters of some actions in actors or transactional memory, if you can immediately use a more efficient atomic? And in general, in trivial cases, it makes no sense to force-initialize all-all-all variables, do a bunch of additional checks, and so on. Let in the end we get not 10% acceleration, but 2-5%. This is also not bad at all, if it only took 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?

Aside from the trade-off between speed and safety, I also have questions about the design of the language itself. In particular, regarding the five types of pointers. On the one hand, it is not bad when the programmer thinks about where the variables are, on the stack or heap, and several threads may or may not work with them at the same time. But on the other hand, imagine that you are writing a program, and it turned out that the variable should not live on the stack, but on the heap. You rewrite everything to use Box. Therefore, you understand that you really need Rc or Arc. Rewrite again. And then you rewrite it again to an ordinary variable on the stack. All this - without a normal IDE at hand. And regular season 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 display all these Boxes, Arc, and so on automatically. But for some reason this part of the work is left to the programmer. Much more convenient it would be easy to write val (in the third millennium!), and where necessary, explicitly indicate Box or R. The Rust developers in this sense screwed up the whole idea.

Because of this, in particular, the scope of Rust is greatly narrowed. No one in their right mind would write web and server side 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 futures) looks much more attractive for these tasks. With futures, 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 sawing into microservices, and by other methods. And yes, no one will translate Rust into JavaScript, write scripts for layout in AWS, or use it as a query language for MongoDB. They will hardly write for Android either, but for another reason - there is much more than one architecture, with JVM it is much easier. If you suddenly thought that Rust was "suitable for all tasks", I have to disappoint you.

Well, to the heap:

  • Macros, as a backup to the excessive verbosity caused by the absence of normal exceptions. I already wrote 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 strongly 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, the same situation.
  • Like many new languages, Rust follows 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 on what to use and what not.
  • If we were to follow the path of simplification, then we would have thrown out all these extensions of the language. 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 is suddenly privileged to release a very deep data structure. While he walks through the maze of dead links, the threads that depend on him patiently blunt. The same problem exists in Erlang with its small heaps, I myself have observed it more than once. Smart pointers have their own problems, the same memory fragmentation and leaks. I forgot the wikpointer in the cyclic structure, that's all. And this is in a language that claims to be safe. If you want a predictable GC time, either study the behavior of your application under load and take action (remember at least the same object pools) if the GC time does not suit you, or manually manage the memory.
  • Has anyone seen a strict description of Rust semantics? Does he at least have a memory model? Also to me a "safe" language that "proves correctness" of programs, which actually can interpret the source code in ten different ways, ha!
  • I can't help but remind you that the problem is almost always people, not technology... If you get bad C ++ or Java code that suddenly slows down, it's not because the technology is bad, but because you haven't learned how to use it properly. You will also be unhappy with Rust, but for different reasons. Isn't it easier to learn how to use the more popular tools and start to love them?

In general, for the next 5 years I will be better off investing my time in learning C / C ++ than Rust. C ++ - this is an industry standard... Various problems have been successfully solved in this language for over 30 years. And Rust and others like it are incomprehensible toys with a vague future. There has been talk about the imminent death of C ++ since at least the 2000s, but writing in C / C ++ during this time has become no less. Quite the opposite. And we see that the language is developing (C ++ 11, C ++ 14), new tools appear for it (remember, at least CLion and Clang), and there are just a bunch of corresponding vacancies.

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

Save your time and health, you don't have as many of them as it seems!

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

About language

Rust is a procedural programming language that supports a variety of coding styles. Developer Graydon Horus began creating the language in 2006, and Mozilla joined the project three years later. In 2010, Rust was presented at the Mozilla Summit. In the same year, the development was transferred to a compiler written in Rust. The compiler used the LLVM universal program analysis and transformation system 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 Khor laid such concepts as:

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

An example of one of the first Rust codes

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

  • Redundancy of the code.
  • Lack of literature for language learning.
  • Clarity in entering compilation options. This does not always suit experienced programmers, since there are no similar rules in other languages.

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

Comparing Rust to C ++

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

  • Access to remote memory. In C ++, deleting a variable can cause a number of problems. Complications like this are not possible in Rust because there are no commands for deleting memory. The compiler of the successor will report that the written code contains an error, and the C ++ compiler will output the result without the deleted 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 insulates main code from unsafe code. In the future, when reviewing the code, this allows you to narrow down the search for vulnerabilities.

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

Perspectives

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

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

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

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



Today, Rust syntax is supported in vim and emacs using the syntax files supplied with the compiler.
There are also syntax packages for the popular proprietary Sublime Text 2 editor and the free Kate editor. There is no Rust support in the IDE yet. Debugger support appears to be lacking as well.

The following utilities are supplied with the rustc compiler:
> rustdoc- utility for automatic generation of documentation from source code like Doxygen;
> rustpkg- a package manager that allows you to easily install additional packages and libraries;
> rusti- the so-called REPL utility (read-eval-print-loop). In essence, it is a test interpreter that takes a Rust expression from the command line, compiles it to the internal LLVM representation, executes and outputs the result;
> rust- a universal utility that launches other utilities or the compiler, depending on the parameters. It never worked for me.

All available language documentation is collected on the official website www.rust-lang.org. There is a detailed manual (http://static.rust-lang.org/doc/tutorial.html) - comprehensive formal documentation on all the nuances of syntax, memory model, runtime system, etc., as well as documentation on the built-in core library and the standard library std. All documentation is in English. There are no relevant materials in Russian, and a couple of available review articles have already become very outdated.

Ideology and syntax


Rust is a C-like language that uses curly braces to mark blocks of code. The language is "multi-paradigm", i.e. allows you to write code in an imperative procedural, object-oriented, concurrent, or functional manner. Rust compiles to native binaries on any supported platform (uses LLVM as a backend). In theory, Rust code should be as fast as C / C ++ code. Rust is marketed as a system language, but it does not have built-in support for blocks of assembly code as in "true" system languages ​​C, C ++, or D.

Rust's memory model natively does not allow null or dangling pointers and buffer overflows. There is an optional garbage collector that only works within a single thread of code. The language has built-in support for lightweight multitasking and thread-to-thread messaging. Shared memory doesn't exist in Rust. All variables are subdivided into stack variables, heap variables for a given thread, and variables of the so-called "exchange" heap, which can be read by all threads, but cannot be changed by them. This automatically eliminates deadlocks, which are considered the scourge of multi-threaded programming. The ABI of the language is compatible with C, so Rust programs can link against libraries written in C without additional wrappers. For the needs of low-level system programming and to ensure compatibility with C, the language has a special "unsafe" mode without checking the correctness of pointers. In terms of its ideology, Rust is closest to the Go language. Just like in Go, the main emphasis is on the simplicity of multi-threaded programming and the speed of developing large-scale applications, and the syntax is just as unusual and somewhat surprising in some places. At the same time, Rust is not as minimalistic as Go, and claims to be a system language.

Rust's syntax is largely borrowed from C and C ++, with a touch of ideas from Go, C #, Haskell, Python, and Ruby. I will not exhaustively describe the syntax of the language, but will focus only on the most interesting concepts.

Rust is a new experimental programming language developed by Mozilla. The language is compiled and multi-paradigm, it is positioned as an alternative to C / C ++, which is interesting in itself, since there are not so many contenders for competition. Think of 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 applied programming.

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

First impression

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

An example is a little more complicated - the function for calculating the factorial:

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), while argument has no parentheses (similar to Go). The compactness of the keywords is also immediately evident. The creators of Rust really purposefully made all the keywords as short as possible, and to be honest, I love that.

Small but interesting syntactic features

  • You can use underscores in numeric constants. A handy thing, now this feature is being added to many new languages.
    0xffff_ffff_ffff_ffff_ffff_ffff
  • Binary constants. Of course, a real programmer should convert bin to hex in his head, but this is more convenient! 0b1111_1111_1001_0000
  • The bodies of any operators (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 operators if, while and the like do not need to be enclosed in parentheses.
  • in many cases, blocks of code can be thought of as expressions. In particular, this is possible for example:
    let x = if the_stars_align () (4) else if something_else () (3) else (0);
  • syntax for declaring functions - first the fn keyword, then the list of arguments, the type of the argument is indicated after the name, then, if the function returns a value, the arrow "->" and the type of the return value
  • variables are declared in a similar way: the let keyword, 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.
  • base type names are the most compact ones I've 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 tools for debugging programs:
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 false, exits the current process
Keyword note allows you to display additional information in case of an 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, "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, for example
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 used in C, C ++, C #, and Java does not allow such constructs. With nominative typing, each structure is a unique type, incompatible with other types by default.

Structures in Rust are called "records". There are also tuples - these are the same records, but with unnamed 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 - of the std :: vector type from stl. When initializing with a list, square brackets are used, not curly brackets like 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 the clutter of "template" from C ++. Function templates and data types 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 the 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 union and enumeration). Or, theoretically, 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 with its own "constructor".
Another interesting example is a recursive structure that defines 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, you can consider the matching pattern as an improved switch. The keyword alt is used, followed by the analyzed expression, and then in the body of the operator - patterns and actions in case of coincidence with patterns.
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 "pattern" you can use not only constants (as in C), but also more complex expressions - variables, tuples, ranges, types, placeholders ("_"). You can write additional conditions using the when clause 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 "cic" pointers, Rust supports special smart pointers with built-in reference counting - Shared boxes and 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 - move:
let x = ~ 10; let y<- x;
after such a move, the x pointer is deinitialized.

Closures, partial application, iterators

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

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 make_plus_function that takes one argument "x" of type int and returns a function of type "int-> int" (where lambda is a keyword). The body of the function describes this very function. The absence of the "return" operator is a little confusing, however, for FP this is a common thing.

2. Keyword block used to declare a functional type - a function argument that can be substituted for 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 with a block as input - in fact, a lambda function of the "int-> int" type, and a vector of the int type (more on the syntax of vectors later). The "block" itself in the calling code is written using a somewhat unusual syntax (| x | x + 1). Personally, I like C # lambdas better, the | stubbornly perceived as a bitwise OR (which, by the way, is also in Rust, like all the good old C operations).

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

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

To make it clearer, I will say right away that this can be done in plain 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 a procedural one (by the way, it is not clear from the above example how to make a partial application to get a function with no arguments)

Another example: the add function is declared with two int arguments, returning an int. Next, the single_param_fn functional type is declared, which has one int argument and returns an int. Using bind, two function objects, add4 and add5, are declared, built on top of the add function, which has partial 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);

Functional 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 functions other than pure ones). Such functions are distinguished by the pure keyword.
pure fn lt_42 (x: int) -> bool (ret (x< 42); }
Predicates are pure functions that return a bool type. Such functions can be used in the typestate system (see below), that is, they can be called at compile time for various static checks.

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

Attributes

A concept similar to C # attributes (and even with similar syntax). Special thanks to the developers for this. As you might expect, attributes add meta information to the entity they annotate.
# fn register_win_service () (/ * ... * /)
Another variant of the attribute syntax was invented - the same line, but with a semicolon at the end, annotates the current context. That is, whatever matches the closest 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 entry function, its own stack, means of interacting with other tasks - channels for outgoing information and ports for incoming, and owns some part of the objects in the dynamic heap.
Many Rust tasks can exist within a single operating system process. Rust tasks are "lightweight": each task consumes less memory than an OS process, and switching between them is faster than switching between OS processes (this probably means "threads").

The task consists of at least one function with no arguments. The task is started using the spawn function. Each task can have channels through which it communicates 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 transferring unsigned bytes.
To send to a channel, the send function is used, the first argument of which is the channel, and the second is the value to be sent. In fact, this function places the value in the internal buffer of the channel.
Ports are used to receive data. Port is a templated port type parameterized by the port data type: port is the port for receiving unsigned bytes.
To read from ports, the recv function is used, the argument of which 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 goes into a pending state until another task sends data on the channel associated with the port.
Binding channels to ports is very simple - by initializing a 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 multiple ports at the same time.

Typestate

I haven’t found a generally accepted translation of the concept of “typestate” into Russian, so I will call it “type states”. The essence of this feature is that in addition to the usual type checking accepted in static typing, additional context checks are possible at the compilation stage.
In one form or another, the states of types are familiar to all programmers - according to the messages of the compiler, "the variable is used without initialization." The compiler determines the places where a variable that has never been written to is used for reading, and issues a warning. More generally, this idea looks like this: each object has a set of states that it can take. In each state, valid and invalid operations are defined for this object. And the compiler can check whether a specific operation on an object is permissible in a particular place in the program. It is important that these checks are performed at compile time.

For example, if we have an object of the "file" type, then it can have a state of "closed" and "open". And the read operation from the file is invalid if the file is closed. In modern languages, it is common for a read function to either throw an exception or return an error code. The type state system could detect such an error at compile time - just as the compiler determines that a read operation of 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.

In Rust, there is 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 compile time for the purpose of statically checking 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 the following way:
fn test (int x, int y): is_less_than (x, y) (...)

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

That's all. It is possible that I missed some interesting points, but the article was already bloated. If you want, you can build the Rust compiler now and try to play around with various examples. For assembly information, see

I'm new to Rust, but it's quickly becoming my favorite programming language. While writing small projects in Rust is usually less ergonomic and more time consuming (at least with me behind the wheel), it challenges the way I think about program design. My battles with the compiler become less frequent after I learn something new.

The Rust community has recently focused a lot of its efforts on asynchronous I / O, implemented as the Tokio library. And that is great.

Many of the community members, those who have not worked with web servers and related things, are not clear what we want to achieve. When these things were discussed in the 1.0 era, I too had a vague idea of ​​it, having never worked with it before.

  • What it is - Async I / O?
  • What are coroutines ( coroutines )?
  • What are lightweight streams ( lightweight threads )?
  • What are futures? ( futures )?

  • How do they fit together?

I will show you how to write a small program that downloads the feed ( feed) in JSON format, parses and outputs the list of notes to the console in formatted form.

Everything has resulted in a very laconic code. How? Look under the cut.

The unsafe keyword is an integral part of the design of the Rust language. For those unfamiliar with it: unsafe is a keyword that, in simple terms, is a way to bypass type checking ( type checking) Rust.

The existence of the unsafe keyword is initially a surprise to many. Indeed, isn't it a feature of Rust that programs don't crash from memory errors? If so, why is there an easy way to bypass the type system? This may seem like a design flaw in the language.

Yet unsafe is not a disadvantage in my opinion. In fact, it is an important part of the language. unsafe acts as some kind of outlet valve - this means that we can use the type system in simple cases, however, allowing us to use all sorts of tricky tricks that you want to use in your code. We only require that you hide your unsafe code behind safe external abstractions.

This note introduces the unsafe keyword and the idea of ​​limited "insecurity". In fact, this is the harbinger of a note that I hope to write a little later. She discusses the Rust memory model, which specifies what can and cannot be done in unsafe code.

As a newbie to Rust, I got confused about the different ways to represent strings. The Rust book has a chapter called "References and Borrowing," which uses three different types of string variables in the examples: String, & String, and & str.

Let's start with the difference between str and String: String is an extensible, heap-allocated data structure, whereas str is an immutable, fixed-length string. somewhere in mind.

Many programmers already know how to program in object-oriented languages. Rust is not a classic object-oriented language, but basic OOP tools can be used in it.

In this article, we'll take a look at how to program in Rust in an OOP style. We will do this using an example: we will build a class hierarchy in a training problem.

Our task is to work with geometric shapes. We will display them in text form and calculate their area. Our set of shapes - rectangle, square, ellipse, circle.

Rust is an elegant language that is somewhat different from many other popular languages. For example, instead of using classes and inheritance, Rust offers its own trait-based type system. However, I believe that many newer Rust programmers (like myself) are not familiar with well-established design patterns.

In this article, I want to discuss the design pattern new type(newtype), as well as the From and Into traits that aid in type conversion.

I've been thinking a lot lately about design patterns and techniques that we use in programming. It's really great to start researching a project and see familiar patterns and styles that you've come across more than once. This makes it easier to understand the project and makes it possible to speed up the work.

Sometimes you are working on a new project and you realize that you need to do something in the same way as you did in the previous project. It may not be part of the functionality or a library, it may be something that cannot be wrapped in a nifty macro or small container. It might just be a design pattern or structural concept that solves the problem well.

One interesting pattern often applied to such problems is "State Machine". I propose to spend a little time to understand what exactly is meant by this phrase, and why it is so interesting.

Below is a graphical description of moving, copying and borrowing in the Rust programming language. Basically, these concepts are specific to Rust only and are often a stumbling block for newbies.

To avoid confusion, I've tried to keep the text to a minimum. This note is not a substitute for various tutorials, and is only made for those who believe that information is perceived visually more easily. If you are just starting to learn Rust and find these graphs useful, then I would recommend that you mark your code with similar schemes to better solidify the concepts.

Implementing the arithmetic of natural numbers using Peano numbers is a popular problem in programming learning. I was wondering if it is possible to implement them in Rust.

Thus, my task is to write and add natural numbers with type checking.

According to Wikipedia "Peano's axioms are one of the systems of axioms for natural numbers, introduced in the 19th century by the Italian mathematician Giuseppe Peano."

We are interested in two of them - with which you can enter and use natural numbers:

  • 1 is a natural number
  • The number following the natural is also natural.

Let's write it verbatim to rust with:

1 2 3 4 enum Nat (Zero, Succ (Nat))

Nat is either zero or the next natural number.

Comment: the futures-rs project has been reorganized and many things have been renamed. Links have been updated where possible.

Getting started with futures

This document will help you explore the Rust programming language container, futures, which provides zero-cost implementations of futures and streams. Futures are available in many other programming languages ​​such as C ++, Java, and Scala, and the futures container draws inspiration from the libraries of those languages. However, it is ergonomic and adheres to the zero-cost abstraction philosophy inherent in Rust, namely that creating and composing futures does not require memory allocations, and only one allocation is required for the Task that manages them. Futures should be the backbone of Rust's asynchronous composable high performance I / O, and early benchmarks show that a simple HTTP server built on futures is really fast.

This documentation is divided into several sections:

  • “Hello, world!”;
  • future type;
  • the Stream trait;
  • specific futures and stream;
  • return of futures;
  • Task and future;
  • local task data.

Comment: the futures-rs project has been reorganized and many things have been renamed. Links have been updated where possible.

One of the major gaps in the Rust ecosystem was fast and efficient asynchronous I / O... We have a solid foundation from the mio library, but it's very low-level: we have to manually create state machines and juggle callbacks.

We would like something higher level, with better ergonomics, but with good composability by supporting an ecosystem of asynchronous abstractions working together. Sounds very familiar: the same goal was pursued by the implementation futures(or promises) to many languages ​​that support syntactic sugar in the form async / await on the top.

The primitive integer types supported by processors are a limited approximation to the infinite set of integers that we are used to operating in real life. This limited representation does not always match "real" numbers, for example 255_u8 + 1 == 0. Often the programmer forgets about this difference, which can easily lead to bugs.

Rust is a programming language that aims to protect against bugs, it focuses on preventing the most insidious of them - memory errors, but also tries to help the programmer avoid other problems: ignoring errors and, as we will see, integer overflows.

A little about Iron

Iron is a high-level web framework written in the Rust programming language and built on top of another notorious hyper library. Iron is designed to take full advantage of the benefits that Rust has to offer. Iron tries to avoid blocking operations in its core.

Philosophy

Iron is built on the principle of expandability as much as possible. He introduces concepts for extending his own functionality:

  • "Intermediate" traits are used to implement end-to-end functionality in request processing;
  • modifiers - used to modify requests and responses in the most ergonomic way.

You will get acquainted with the basic part of modifiers and intermediate traits in the course of the article.

Project creation

First, let's create a project with Cargo using the command:

After compiling, we get the corresponding executable file:

1 2 3 $ rustc hello.rs $ du -h hello 632K hello

632 kilobytes for a simple print ?! Rust is marketed as a systems language that has the potential to replace C / C ++, right? So why not check out a similar program with your nearest competitor?

It is widely believed in our environment that one of the advantages of the garbage collector is the ease of developing high-performance lock-free data structures. Manual memory management in them is not easy, and GC easily solves this problem.

This post will show that using Rust it is possible to build a memory management API for concurrent data structures that:

  • Make it possible to implement a lock-free data structure like the GC does;
  • Create static protection against misuse of the memory management scheme;
  • Will have comparable overheads to GC (and more predictable).

In the tests I'll show below, Rust easily outperforms Java's lock-free queue implementations, and the Rust implementation itself is easy to write.

I have implemented an "epoch-based memory reclamation" scheme in the new Crossbeam library, which is now ready for use with your data structures. In this post, I will talk about lock-free data structures, the epoch algorithm, and the internal Rust API.

Memory access errors and memory leaks are the two categories of bugs that get the most attention, so a lot of effort goes into preventing or at least minimizing them. Although their name suggests a similarity, they are in some way diametrically opposed and the solution of one of the problems does not relieve us of the second. The widespread adoption of managed languages ​​lends credence to this idea: they prevent some memory access errors by taking on the job of freeing memory.

Simply put: a memory access violation is some kind of action with incorrect data, and a memory leak is absence certain actions with correct data... In tabular form:

I have a few thoughts about learning programming languages.

First, we're getting it wrong. I'm sure you experienced the same feeling. You are trying to learn a new language and do not quite understand how everything works in it. Why is one syntax used in one place and another in another? All these oddities are annoying, and in the end we return to the familiar language.

I believe that our perception of languages ​​is playing a cruel joke on us. Think back to the last time you discussed a new language. Someone mentioned it, and someone else asked about its speed, syntax, or the available web framework.

It's a lot like talking about cars. Have you heard about the new UAZ Rybak? How fast is he? Will I be able to ride it across the lake?

When we speak of languages ​​in a similar way, we mean that they are interchangeable. Like machines. If I know how to drive Lada Saransk, then I can drive UAZ Rybak without any problems. The only difference is in speed and dashboard, right?

But imagine what a PHP car would look like. Now imagine how different a Lisp car would be. Changing from one to the other will require much more than learning which button controls the heating.

Note: This article assumes that the reader is familiar with Rust FFI (translation), endianess, and ioctl.

When creating bindings to C code, we will inevitably come across a structure that contains a union. Rust lacks built-in support for joins, so we'll have to strategize ourselves. In C, a union is a type that stores different types of data in the same area of ​​memory. There are many reasons to choose concatenation, such as converting between binary integers and floating point numbers, implementing pseudo-polymorphism, and direct bit access. I will focus on pseudo-polymorphism.