📜 ⬆️ ⬇️

Release Rust 1.26

The Rust development team is pleased to announce the release of a new version of Rust: 1.26.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.26.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.26.0 can be found on GitHub.


What is included in the stable version 1.26.0


The last few issues had a number of relatively minor improvements. Nevertheless, we continued to work on many other things and now they are starting to come out in a stable version. Version 1.26 is perhaps the richest in innovations since the release of Rust 1.0. Let's consider them!


The second edition of the book "Rust programming language"


For nearly 18 months, Carol, Steve, and others have been working on a complete reworking of the book "Rust Programming Language". Since the writing of the first book, we have learned a lot about how people learn Rust, so the new version of the book is now better in all respects.


Earlier, a draft of the second edition has already been published on the website with a statement that this is an unfinished version. Now, small final edits are made to the book and it is being prepared for printing. So from this issue we recommend reading the second edition instead of the first. You can find it on doc.rust-lang.org or get it locally by running rustup doc --book .


By the way, about printing: if you don’t feel sorry for trees, then you can order a paper version of the book on NoStarch Press . The content is identical, but you get either a real physical copy of the book to put it on the shelf, or a perfectly crafted PDF. All proceeds will go to charity.


impl Trait


Finally, we have impl Trait ! This functionality has been very much in demand for a long time, because it provides an opportunity known as "existential types". However, it only sounds scary, the essence of the idea is simple:


 fn foo() -> impl Trait { // ... } 

This type signature says: " foo is a function that takes no arguments and returns a type that implements the Trait type." That is, we do not indicate which type of return foo actually has, but only indicate that it implements a certain type. You may ask how this differs from the use of types of objects:


 fn foo() -> Box<Trait> { // ... } 

This is the correct code and this method also works, but it is not good for all situations. Suppose we have a type of Trait , which is implemented for both i32 and f32 :


 trait Trait { fn method(&self); } impl Trait for i32 { //   } impl Trait for f32 { //   } 

Consider the function:


 fn foo() -> ? { 5 } 

We want to specify a certain type of result. Previously, only the variant with the object type was possible:


 fn foo() -> Box<Trait> { Box::new(5) as Box<Trait> } 

But here Box used, which entails memory allocation in the heap. In fact, we do not want to return any dynamically defined data, so dynamic dispatching here only harms. Instead, in Rust 1.26 you can write this:


 fn foo() -> impl Trait { 5 } 

This does not create a type-object and is more like if we wrote -> i32 , but only with reference to the part related to Trait . We get static dispatching, but with the ability to hide the real type.


How is this useful? One of the good uses is closures. Do not forget that closures in Rust always have a unique, not recordable type that implements the type Fn . This means that if your function returns a closure, you can do this:


 //  fn foo() -> Box<Fn(i32) -> i32> { Box::new(|x| x + 1) } //  fn foo() -> impl Fn(i32) -> i32 { |x| x + 1 } 

No packaging and no dynamic dispatch. A similar situation occurs when iterators are returned. Not only do iterators often include closures, they can also be nested into each other, resulting in fairly deeply nested types. For example:


 fn foo() { vec![1, 2, 3] .into_iter() .map(|x| x + 1) .filter(|x| x % 2 == 0) } 

when compiling will give an error:


 error[E0308]: mismatched types --> src/main.rs:5:5 | 5 | / vec![1, 2, 3] 6 | | .into_iter() 7 | | .map(|x| x + 1) 8 | | .filter(|x| x % 2 == 0) | |_______________________________^ expected (), found struct `std::iter::Filter` | = note: expected type `()` found type `std::iter::Filter<std::iter::Map<std::vec::IntoIter<{integer}>, [closure@src/main.rs:7:14: 7:23]>, [closure@src/main.rs:8:17: 8:31]>` 

This 'found type' is huge because each adapter in the chain adds a new type. In addition, we also have a closure here. Previously, we had to use type-objects in such cases, but now we can just write


 fn foo() -> impl Iterator<Item = i32> { vec![1, 2, 3] .into_iter() .map(|x| x + 1) .filter(|x| x % 2 == 0) } 

and it's done. You can work with futures in the same way.


It is important to note that sometimes types of objects are still needed. You can use impl Trait only if your function returns one type; if you want to return a few, then you need dynamic dispatching. For example:


 fn foo(x: i32) -> Box<Iterator<Item = i32>> { let iter = vec![1, 2, 3] .into_iter() .map(|x| x + 1); if x % 2 == 0 { Box::new(iter.filter(|x| x % 2 == 0)) } else { Box::new(iter) } } 

Here, the filter iterator may or may not be returned. There are two different types that can be returned, and therefore we must use a type-object.


And finally: for syntactic symmetry, you can use impl Trait also in arguments. I.e:


 //  fn foo<T: Trait>(x: T) { //  fn foo(x: impl Trait) { 

can improve the look of short signatures.


Note for those who understand the theory of types: there is not an existential, but a universal type. In other words, impl Trait is universal at the entrance to a function, but existential at the exit.

match matches match


Have you ever tried to use match to link to Option ? For example, in a similar code:


 fn hello(arg: &Option<String>) { match arg { Some(name) => println!("Hello {}!", name), None => println!("I don't know who you are."), } } 

If you try to compile it in Rust 1.25, then you will get the following error:


 error[E0658]: non-reference pattern used to match a reference (see issue #42640) --> src/main.rs:6:9 | 6 | Some(name) => println!("Hello {}!", name), | ^^^^^^^^^^ help: consider using a reference: `&Some(name)` error[E0658]: non-reference pattern used to match a reference (see issue #42640) --> src/main.rs:7:9 | 7 | None => println!("I don't know who you are."), | ^^^^ help: consider using a reference: `&None` 

Yes of course. Let's change the code:


 fn hello(arg: &Option<String>) { match arg { &Some(name) => println!("Hello {}!", name), &None => println!("I don't know who you are."), } } 

We added & as requested by the compiler. Let's try to compile again:


 error[E0507]: cannot move out of borrowed content --> src/main.rs:6:9 | 6 | &Some(name) => println!("Hello {}!", name), | ^^^^^^----^ | | | | | hint: to prevent move, use `ref name` or `ref mut name` | cannot move out of borrowed content 

Yes of course. Let's tame the compiler, following his advice:


 fn hello(arg: &Option<String>) { match arg { &Some(ref name) => println!("Hello {}!", name), &None => println!("I don't know who you are."), } } 

Now the compilation is successful. We had to add two & one ref . But most importantly, none of this was truly useful to us, as programmers. Of course, at first we forgot & , but does it matter? We needed to add ref to get a reference to the value stored inside Option , but we could not do anything else but to get the reference, since we cannot move the value behind &T


So, starting with Rust 1.26, the original code without & and ref will just compile and do exactly what you expect. In short, the compiler will automatically reference or dereference references in the match construct. So when we say


  match arg { Some(name) => println!("Hello {}!", name), 

the compiler will automatically refer to Some by reference, and since this will be a borrowing, the name will be associated with the value as a ref name , also automatically. If we changed the value:


 fn hello(arg: &mut Option<String>) { match arg { Some(name) => name.push_str(", world"), None => (), } } 

the compiler would automatically perform the variable borrowing, and the name would turn out to be associated with the value as ref mut .


We think that this will get rid of the particularly painful routine code of both novice and experienced developers. The compiler will simply take over this job, no longer requiring you to write such routine code.


main can return Result


Speaking of the annoying routine code: since Rust uses the Result type to return errors and ? To simplify their processing, a common pain point for newcomers to Rust is trying to use it ? in main :


 use std::fs::File; fn main() { let f = File::open("bar.txt")?; } 

This generates an error like "error [E0277]: the ? Operator can only be used in a function that returns Result ". Which many people are forced to write similar code :


 fn run(config: Config) -> Result<(), Box<Error>> { // ... } fn main() { // ... if let Err(e) = run(config) { println!("Application error: {}", e); process::exit(1); } } 

Our run function contains all the real logic, and main calls run , checks if an error has occurred and shuts down. We only need this second function because main cannot return a Result , but would we like to use it ? in their logic.


In Rust 1.26 you can now declare main , which returns Result :


 use std::fs::File; fn main() -> Result<(), std::io::Error> { let f = File::open("bar.txt")?; Ok(()) } 

Now it works as it should! If main returns an error, it will terminate with an error code and print debug information about the error.


Closed ranges with ..=


Long before Rust 1.0, you could create semi-open ranges with .. , for example:


 for i in 1..3 { println!("i: {}", i); } 

This code will type i: 1 , and then i: 2 . In Rust 1.26 you can now create a closed range, for example:


 for i in 1..=3 { println!("i: {}", i); } 

This code will type i: 1 , then i: 2 , like the previous one, but also i: 3 ; three - also included in the range. Closed ranges are especially useful for iterating over all possible values. For example, here is an amazing program on Rust:


 fn takes_u8(x: u8) { // ... } fn main() { for i in 0..256 { println!("i: {}", i); takes_u8(i); } } 

What does this program do? Answer: nothing. The warning we get when compiling tells us why:


 warning: literal out of range for u8 --> src/main.rs:6:17 | 6 | for i in 0..256 { | ^^^ | = note: #[warn(overflowing_literals)] on by default 

This is correct, since i type u8 , which is full, and this is the same as writing for i in 0..0 , so the loop is executed zero times.


However, with closed ranges you can fix this:


 fn takes_u8(x: u8) { // ... } fn main() { for i in 0..=255 { println!("i: {}", i); takes_u8(i); } } 

This code will print the 256 lines you expected.


Basic Sample Slices


Another long-awaited innovation is the “slice patterns”. It allows you to match slices with a sample, just as you are matching other types of data with a pattern. For example:


 let arr = [1, 2, 3]; match arr { [1, _, _] => "  ", [a, b, c] => "  - ", } 

In this case, we know that arr has a length of three, and therefore we need three elements inside [] . We can also match when we do not know the length:


 fn foo(s: &[u8]) { match s { [a, b] => (), [a, b, c] => (), _ => (), } } 

Here we do not know how long s , so we can write the first two patterns,
each of which is designed for a different length. We also need the option
_ since we do not cover all possible length cases, but we cannot!


Speed ​​increase


We continue to improve compiler speeds. We found that deep
nested types in some cases became non-linear, which was corrected . After this fix, along with which many other minor fixes were released, we observed a reduction in compile time of up to 12%. In the future we will improve more!


128-bit integers


Finally, one very simple improvement: now Rust has 128-bit integers!


 let x: i128 = 0; let y: u128 = 0; 

They are twice the size of u64 and therefore may contain large values. Namely:



Whew!


See the release notes for details.


Library stabilization


We have stabilized fs::read_to_string , which is more convenient than File::open and io::Read::read_to_string for easy reading into the memory of the entire file at once:


 use std::fs; use std::net::SocketAddr; let foo: SocketAddr = fs::read_to_string("address.txt")?.parse()?; 

Now you can format the output of hexadecimal numbers with Debug :


 assert!(format!("{:02x?}", b"Foo\0") == "[46, 6f, 6f, 00]") 

Trailing commas are now supported by all macros in the standard library .


See the release notes for details.


Improvements in Cargo


In this release, Cargo did not receive significant changes in functionality, but received a number of improvements in stability and performance. Cargo should now handle dependencies from lock files even faster and smarter, and also require less manual calls to the cargo update . Cargo executable now has the same version as rustc .


See the release notes for details.


Developers 1.26.0


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


Thank!


Authors translation: freecoder_xx and ozkriff .


')

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


All Articles