Rust 1.26リリース

Rust開発チームは、Rustの新しいバージョン1.26.0のリリースをお知らせします。 Rustは、セキュリティ、速度、および並列コード実行を目的としたシステムプログラミング言語です。







rustupを使用して以前のバージョンのRustがインストールされている場合、Rustをバージョン1.26.0にアップグレードするには、次を実行するだけです。







$ rustup update stable
      
      





rustupをまだインストールしていない場合は、当社のWebサイトの対応するページからインストールできます。 詳細なRust 1.26.0リリースノートはGitHubで入手できます。







安定バージョン1.26.0に含まれるもの



最後のいくつかのリリースでは、いくつかの比較的小さな改善が行われています。 しかし、私たちは他の多くのことに取り組み続け、現在それらは安定したバージョンでリリースされ始めています。 バージョン1.26は、Rust 1.0のリリース以来、間違いなく最も豊富なイノベーションです。 それらを見てみましょう!







Rustプログラミング言語の第2版



ほぼ18か月間、キャロル、スティーブなどが本「Rust Programming Language」の完全な改訂に取り組んできました。 最初の本が書かれて以来、私たちは人々がどのようにRustを学ぶかについて多くのことを学びました。そのため、本の新しいバージョンはあらゆる点で優れています。







以前、第2版のドラフトはすでに不完全なバージョンであることを示すWebサイトで公開されていました。 現在、この本には最終的な小さな変更が含まれており、出版の準備が進められています。 したがって、この問題から、最初の版ではなく2番目の版を読むことをお勧めします。 doc.rust-lang.orgで見つけるか、 rustup doc --book



実行してローカルで取得できます







印刷といえば、木々が気に入らなければNoStarch Pressで本の紙版を注文できます。 内容は同じですが、本の実際の物理的なコピーを入手して棚に置くか、優れたPDFを入手できます。 すべての収益は慈善団体に寄付されます。







impl Trait





最後にimpl Trait



! この機能は、「存在型」として知られる機会を提供するため、長い間非常に需要がありました。 ただし、これは怖いだけで、アイデアの本質は単純です。







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





この型シグネチャは、「 foo



は引数をとらない関数であり、 Trait



を実装する型を返します。」 つまり、リターンfoo



実際のタイプを正確に示すのではなく、特定のタイプを実装することだけを示します。 あなたはこれが特性オブジェクトを使用することとどう違うのか尋ねるかもしれません







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





これは正しいコードであり、このメソッドも機能しますが、すべての状況に適しているわけではありません。 i32



f32



両方に実装されているTrait



あるとします:







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





機能を考えてみましょう:







 fn foo() -> ? { 5 }
      
      





何らかのタイプの結果を指定する必要があります。 以前は、オブジェクトタイプを持つバリアントのみが可能でした。







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





ただし、ここではBox



使用されており、ヒープ上のメモリ割り当てが必要です。 実際、動的に決定されたデータを返したくないため、ここでの動的ディスパッチは有害です。 代わりに、Rust 1.26では、次のように記述できます。







 fn foo() -> impl Trait { 5 }
      
      





これは特性オブジェクトを作成するものではなく、 -> i32



記述した場合と似ていますが、 Trait



関連する部分についてのみ言及しています。 静的ディスパッチを取得しますが、実際の型を非表示にする機能があります。







これはどのように役立ちますか? 良い使い方の1つはクロージャーです。 Rustのクロージャーには常に、 Fn



型を実装する一意の書き込み不可能な型があることにFn



。 これは、関数がクロージャを返す場合、これを実行できることを意味します。







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





パッケージングも動的なディスパッチもありません。 イテレータを返すときに同様の状況が発生します。 多くの場合、イテレーターにはクロージャーが含まれるだけでなく、入れ子にできるため、かなり深く入れ子になった型になります。 例:







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





コンパイル時にエラーが発生します:







 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]>`
      
      





チェーン内のすべてのアダプターが新しいタイプを追加するため、この「見つかったタイプ」は巨大です。 さらに、ここにもクロージャーがあります。 以前は、このような場合には特性オブジェクトを使用する必要がありましたが、今では次のように書くことができます







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





そして、仕事は完了です。 同様に先物扱うことができます。







時々特性オブジェクトがまだ必要であることに注意することが重要です。 関数が単一の型を返す場合にのみimpl Trait



使用できます。 複数を返す場合は、動的ディスパッチが必要です。 例:







 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) } }
      
      





ここでは、フィルター反復子が返される場合と返されない場合があります。 返される可能性のある2つの異なるタイプがあるため、traitオブジェクトを使用する必要があります。







最後に、構文の対称性のために、引数にもimpl Trait



を使用できます。 それは:







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





短い署名の外観を改善できます。







型理論に精通している人への注意:ここは実存的ではなく、普遍的な型です。 言い換えれば、 impl Trait



は関数への入力では普遍的ですが、出力では存在します。


match



マッチのmatch





Option



を参照するためにmatch



を使用しようとしたことがありますか? たとえば、同様のコードでは:







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





Rust 1.25でコンパイルしようとすると、次のエラーが表示されます。







 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`
      
      





はい、もちろんです。 コードを変更しましょう:







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





必要なコンパイラとして&



を追加しました。 もう一度コンパイルしてみましょう。







 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
      
      





はい、もちろんです。 彼のアドバイスに従って、コンパイラーを落ち着かせましょう。







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





これでコンパイルが成功します。 2つのref



と1つのref



を追加する必要がありました。 しかし、最も重要なことは、プログラマーとして私たちにとってこれが本当に役に立たなかったことです 。 もちろん、最初は&



を忘れていましたが、重要ですか? Option



内に格納された値への参照を取得するためにref



を追加する必要がありましたが、値を&T



を超えて移動できないため、リンクを取得する以外に何もできませんでした&T









したがって、Rust 1.26からは、 ref



ない元のコードがコンパイルされ、期待どおりに正確に実行されます。 要するに、コンパイラーは、 match



構造内のリンクを自動的に参照または逆参照します。 したがって、私たちが話すとき







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





コンパイラーは自動的にSome



を参照で参照します。これは借用であるため、 name



ref name



として値にもバインドします。これも自動的に行われます。 値を変更する場合:







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





コンパイラは可変借入を自動的に実行し、 name



ref mut



として値に関連付けられます。







これにより、初心者と経験豊富な開発者の両方にとって特に苦痛なルーチンコードを節約できると考えています。 コンパイラがこの作業を引き継ぐだけで、そのようなルーチンコードを記述する必要がなくなります。







main



Result



を返すことができます



迷惑なルーチンコードと言えば、RustはResult



型を使用してエラーと?



を返すため?



処理を簡素化するために、Rustの初心者にとっての一般的な問題点は、 ?



main









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





これにより、「エラー[E0277]:?演算子は、 Result



を返す関数でのみ使用でき?



」のようなエラーが生成されます。 多くの人々が同様のコードを書くことを強制する:







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





run



関数には、すべての実際のロジックと、 main



呼び出しrun



が含まれており、エラーが発生したかどうかを確認してシャットダウンします。 main



Result



返すことができないため、この2番目の関数のみが必要?



、使用したいのは?



あなたの論理で。







Rust 1.26では、 Result



を返すmain



宣言できるようになりました。







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





これで正常に機能するようになりました! main



がエラーを返した場合、エラーコードで終了し、デバッグエラー情報を出力します。







..=



閉じた範囲



Rust 1.0よりもずっと前に、 ..



半開範囲を作成できました..



たとえば、







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





このコードはi: 1



、次にi: 2



ます。 Rust 1.26では、次のようにプライベート範囲を作成できるようになりました。







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





このコードはi: 1



、次にi: 2



前のものとして出力しますが、 i: 3



ます。 3-範囲にも含まれます。 閉じた範囲は、可能なすべての値を反復処理する場合に特に役立ちます。 例えば、ここに素晴らしいRustプログラムがあります:







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





このプログラムは何をしますか? 答え:何もありません。 コンパイル時に表示される警告は、次の理由を示しています。







 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
      
      





i



オーバーフローするu8



型であり、これはfor i in 0..0



for i in 0..0



を書き込むのと同じであるため、ループはゼロ回実行されます。







ただし、閉じた範囲ではこれを修正できます。







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





このコードは、期待した256行を出力します。







基本的なスライスパターン



もう1つの待望の革新は「スライスパターン」です。 他のデータ型をパターンにマッピングするのと同じように、スライスをパターンにマッピングできます。 例:







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





この場合、 arr



の長さは3であることがわかっているため、 []



内に3つの要素が必要です。 長さがわからない場合にも一致させることができます。







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





ここではs



長さがわからないので、最初の2つのサンプルを書くことができます。

それぞれが異なる長さに設計されています。 オプションも必要です

_



、可能性のあるすべてのケースをカバーしているわけではありませんが、カバーすることはできません!







スピードアップ



コンパイラの速度を改善し続けています。 深い

場合によっては、ネストされた型が非線形になり、 修正されました 。 他の多くのマイナーな修正がリリースされたこの修正の後、最大12%のコンパイル時間の短縮が観察されました。 将来的にはさらに改善されます!







128ビット整数



そして最後に、1つの非常に簡単な改善:Rustに128ビット整数が追加されました!







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





これらはu64



2倍の大きさであるため、大きな値が含まれる場合があります。 すなわち:









ふう!







詳細については、リリースノートを参照してください。







ライブラリの安定化



File::open



およびio::Read::read_to_string



よりも便利なfs::read_to_string



を安定化して、ファイル全体を一度にメモリに簡単に読み込むことができました。







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





これで、Debugを使用して16進数の出力をフォーマットできます







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





末尾のコンマは、標準ライブラリのすべてのマクロでサポートされるようになりました







詳細については、リリースノートを参照してください。







貨物の強化



このリリースでは、Cargoに重要な機能の変更はありませんでしたが、いくつかの安定性とパフォーマンスの改善が行われました。 Cargoは、ロックファイルからの依存関係をさらに高速かつスマートに処理する必要があります。また、Cargo cargo update



への手動呼び出しが少なくて済みます。 Cargo実行可能ファイルのバージョンはrustcと同じになりました







詳細については、リリースノートを参照してください。







開発者1.26.0



多くの人々がRust 1.26の開発に参加しました。 皆さん一人一人がいなければ、作業を完了できませんでした。







よろしくお願いします!







翻訳の著者: freecoder_xxおよびozkriff








All Articles