Release Rust 1.27

The Rust development team is pleased to announce the release of a new version of Rust: 1.27.0. Rust is a system programming language aimed at security, speed, and parallel code execution.


If you have a previous version of Rust installed using rustup, then to update Rust to version 1.27.0 you just need to run:


$ rustup update stable 

If you have not yet installed rustup, you can install it from the corresponding page of our website. Detailed notes for the release of Rust 1.27.0 can be found on GitHub.


We also want to draw your attention to the following: before the release of version 1.27.0, we found an error in improving match mappings, introduced in version 1.26.0, which can lead to incorrect behaviors. Since it was discovered very late, already in the process of releasing this version, although it is present from version 1.26.0, we decided not to break the routine and prepare a revised version 1.27.1, which will be released soon. And additionally, if required, version 1.26.3. Details can be found in the relevant release notes.


What is included in the stable version 1.27.0


In this issue, there are two major and long-awaited improvements to the language. But first, a small comment on the documentation: search is now available in all the books in the Rust library ! For example, you can find "borrowing" in the book "Rust Programming Language" . We hope this will make it easier to find the information you need. In addition, a new book about rustc . This book explains how to directly use rustc , as well as how to get other useful information, such as a list of all static checks.


SIMD


So, now about the important: from now on in Rust basic possibilities of using SIMD are available ! SIMD means "single instruction stream, multiple data stream" (single instruction, multiple data). Consider the function:


 pub fn foo(a: &[u8], b: &[u8], c: &mut [u8]) { for ((a, b), c) in a.iter().zip(b).zip(c) { *c = *a + *b; } } 

Here we take two integer slices, summarize their elements and put the result in the third slice. The above code demonstrates the easiest way to do this: you need to go through the entire set of elements, put them together and save the result. However, compilers often find a better solution. LLVM often “automatically vectorizes” similar code, where such intricate wording simply means “uses SIMD”. Imagine that sections a and b are both 16 elements long. Each element is u8 , so the slices will contain 128 bits of data each. Using SIMD, we can place both slice a and b in 128-bit registers, put them together in one instruction and then copy the resulting 128 bits into c . It will work much faster!


Although the stable version of Rust has always been able to take advantage of the automatic vectorization, sometimes the compiler is simply not smart enough to understand that it can be used in this case. In addition, not all CPUs support such features. Therefore, LLVM cannot always use them, since your program can run on a variety of hardware platforms. Therefore, in Rust 1.27, with the addition of the std::arch module, it became possible to use these types of instructions directly , that is, now we are not obliged to rely only on intelligent compilation. Additionally, we have the opportunity to choose a specific implementation depending on various criteria. For example:


 #[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "avx2"))] fn foo() { #[cfg(target_arch = "x86")] use std::arch::x86::_mm256_add_epi64; #[cfg(target_arch = "x86_64")] use std::arch::x86_64::_mm256_add_epi64; unsafe { _mm256_add_epi64(...); } } 

Here we use cfg flags to select the correct version of the code depending on the target platform: its own version will be used on x86 , and its own version on x86_64 . We can also choose at runtime:


 fn foo() { #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] { if is_x86_feature_detected!("avx2") { return unsafe { foo_avx2() }; } } foo_fallback(); } 

Here we have two versions of the function: one uses the AVX2 , a specific kind of SIMD that allows 256-bit operations. The macro is_x86_feature_detected! will generate code that will check if the processor supports AVX2, and if so, the function foo_avx2 will be called. If not, we will resort to the implementation without AVX, foo_fallback . So our code will work very fast on processors that support AVX2, but it will also work on other processors, albeit more slowly.


It all looks slightly low-level and uncomfortable - yes, it is! std::arch is exactly the primitives for this kind of thing. We hope that in the future we will nevertheless stabilize the std::simd with high-level capabilities. But the emergence of the basic capabilities of working with SIMD now allows you to experiment with high-level support for various libraries. For example, see the package faster . Here is a snippet of code without SIMD:


 let lots_of_3s = (&[-123.456f32; 128][..]).iter() .map(|v| { 9.0 * v.abs().sqrt().sqrt().recip().ceil().sqrt() - 4.0 - 2.0 }) .collect::<Vec<f32>>(); 

To use SIMD in this code using faster , you need to change it like this:


 let lots_of_3s = (&[-123.456f32; 128][..]).simd_iter() .simd_map(f32s(0.0), |v| { f32s(9.0) * v.abs().sqrt().rsqrt().ceil().sqrt() - f32s(4.0) - f32s(2.0) }) .scalar_collect(); 

It looks almost the same: simd_iter instead of iter , simd_map instead of map , f32s(2.0) instead of 2.0 . But in the end you get a SIMD-certified version of your code.


In addition, you may never write this yourself, but, as always, libraries that you depend on can do. For example, support has already been added regex , and its new version will have SIMD acceleration without you having to do anything at all!


dyn Trait


In the end, we regretted the original syntax of type-objects in Rust. As you remember, for type Foo you can define a type object:


 Box<Foo> 

However, if Foo would be a structure, it would mean simply placing the structure inside Box<T> . When developing the language, we thought that such a similarity would be a good idea, but experience has shown that this leads to confusion. And it's not just Box<Trait> : impl SomeTrait for SomeOtherTrait also a formally correct syntax, but you almost always need to write impl<T> SomeTrait for T where T: SomeOtherTrait instead. It is the same with impl SomeTrait , which looks like it adds methods or a possible default implementation to the type, but in fact it adds its own methods to the type-object. Finally, compared with the recently added impl Trait syntax, the Trait syntax looks shorter and is preferable to use, but in fact this is not always true.


Therefore, in Rust 1.27, we have stabilized the new syntax dyn Trait . Object types now look like this:


 //  =>  Box<Foo> => Box<dyn Foo> &Foo => &dyn Foo &mut Foo => &mut dyn Foo 

Similarly for other pointer types: Arc<Foo> now Arc<dyn Foo> , etc. Due to the requirement of backward compatibility, we cannot remove the old syntax, but we have added a static bare-trait-object check, which by default allows the old syntax. If you want to disable it, then you can activate this check. We thought that with the check enabled by default, too many warnings would now be displayed.


By the way, we are working on a tool called rustfix , which can automatically update your code to newer idioms. He will use similar static checks for this. Follow rustfix in future announcements.

#[must_use] for functions


#[must_use] effect of the attribute #[must_use] been extended: it can now be used for functions .


Previously, it was applied only to types such as Result <T, E> . But now you can do this:


 #[must_use] fn double(x: i32) -> i32 { 2 * x } fn main() { double(4); // warning: unused return value of `double` which must be used let _ = double(4); // (no warning) } 

With this attribute, we also slightly improved the standard library : Clone::clone , Iterator::collect and ToOwned::to_owned will issue warnings if you do not use their return values, which will help you notice expensive operations that you accidentally ignore.


See the release notes for details.


Library stabilization


The following new APIs have been stabilized in this release:



See the release notes for details.


Improvements in Cargo


In this issue, Cargo received two small improvements. First, a --target-dir , which can be used to change the target directory for execution.


Additionally, Cargo’s approach to how to handle targets has been improved. Cargo is trying to discover tests, examples, and executables within your project. However, explicit configuration is sometimes required. But in the initial implementation this was problematic. Let's say you have two examples, and Cargo both detects them. You want to configure one of these, by adding [[example]] to Cargo.toml to specify the parameters for the example. Currently, Cargo will see that you have defined the example explicitly, and therefore will not attempt to automatically detect others. This is a little sad.


Therefore, we 'auto'- Cargo.toml . We cannot correct this behavior without a possible breakdown of projects that recklessly relied on it. Therefore, if you want to configure some goals, but not all, you can set the autoexamples key to true in the [package] section.


See the release notes for details.


Developers 1.27.0


A lot of people participated in the development of Rust 1.27. We could not complete the work without the participation of each of you.


Thank!


From the translator: I express hotel thanks to the members of the ruRust community and personally to ozkriff for their help with translation and proofreading

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


All Articles