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.
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!
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.
..=
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.
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!
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!
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:
u128
: 0 - 340,282,366,920,938,463,463,374,607,431,768,211,455i128
: −170,141,183,460,469,231,731,687,303,715,884,105,728 - 170,141,183,460,469,231,731,687,303,715,884,105,727Whew!
See the release notes for details.
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.
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.
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.
Authors translation: freecoder_xx and ozkriff .
Source: https://habr.com/ru/post/358514/
All Articles