30 Days of Rust - Day 30

これがそれです、最終ブログです。長くて厳しいので、読むのに少し時間を取ってください。心配しないでください、私はまだブログを投稿し続けますが、おそらくRust言語の機能に関してではないでしょう - 私たちは今のところそれから休憩が必要だと思います。

昨日の質問に答えました


答える質問はありません

今日のオープンな質問


オープンな質問はありません

関連型とジェネリクス


Rustでは、ジェネリクスが関数やトレイトの再利用性を向上させる方法として既に見てきました。関連型は再利用性に別のアプローチを提供します。特定のトレイトを与えられたタイプに対して1回だけ実装する必要がある場合があります。これがトレイトに関連型の定義を持つ際により有用になるかもしれません。Rustの本からこの例を考えてみてください:

use std::ops::Add;

#[derive(Debug, Copy, Clone, PartialEq)]
struct Point {
    x: i32,
    y: i32,
}

impl Add for Point {
    type Output = Point;

    fn add(self, other: Point) -> Point {
        Point {
            x: self.x + other.x,
            y: self.y + other.y,
        }
    }
}

fn main() {
    assert_eq!(
        Point { x: 1, y: 0 } + Point { x: 2, y: 3 },
        Point { x: 3, y: 3 }
    );
}

Addの実装ではタイプOutputを宣言しています。実際にやっていることはAddトレイトの関連型を定義していることです。これがなければ、コードはコンパイルできません。関連型はこの関数で契約として機能します。トレイトの定義によると、関連型はadd関数の戻り値として機能します。こちらがトレイトの宣言です:

trait Add<Rhs=Self> {
    type Output;

    fn add(self, rhs: Rhs) -> Self::Output;
}

Addトレイトの興味深い点は、デフォルトの関連型も持っていることです。これはRhs別名入力引数によって表されます。

この文脈でデフォルトのタイプを提供することは、ライブラリの作者に柔軟性を提供します:ライブラリの多くのユーザーは一通りの方法でトレイトを使用することが多いです。この典型的な使用パターンはトレイトのデフォルトタイプを通じて表現されることができます。しかしながら、これをパラメーターとして設定することによって、ライブラリを予見していなかった方法で使用するチャンスをパワーユーザーに提供することができます。

関数と関連関数の名前が重複しているため、完全修飾構文が必要です


RustではPythonのように、オブジェクトに同じ名前のメソッドを複数定義することに制限はありません。それはおそらく、過負荷のメソッドを除いて、そのユーザーがそうすることを意図したプログラミング言語はないからでしょう。

しかし、Rustでは、structが同じ名前の複数のメソッドまたは関連関数を持っているという比較的一般的な状況があります。外部ライブラリからtraitを実装する際、既に定義しているメソッド名を使用することが強制されるかもしれません。

メソッドコールのための構文

多スレッドについて話したときを思い出してください。私たちはSend(およびSync)トレイトについて簡単に話しました。標準ライブラリからだけでなく、異なる方法でスレッドをインスタンス化するサードパーティライブラリからもSendを実装したコードを想像してみてください。突然、sendメソッドの2つのバージョンがあります。

これらのメソッドを呼び出すとき、完全修飾構文を使用する必要があります。これはtraitmethod nameから構成されます:

let thread_struct = MyCustomThread::new();

StdLibrarySendTrait::send(&thread_struct);
ExternalLibrarySendTrait::send(&thread_struct);

関連関数のための構文

structのメソッドが最初の引数としてstructへの参照を含むため、完全修飾構文を使用してメソッドを呼び出すとき、Rustはselfのタイプを知っているのでどの実装ブロックが呼ばれているかわかります:

impl ExternalLibrarySendTrait for MyCustomThread {
    fn send(&self) {
        // 何かする
    }
}

impl ExternalLibrarySendTrait for SomeOtherType {
    fn send(&self) {
        // 何かする
    }
}

ExternalLibrarySendTrait::send(&thread_struct);

しかし、selfを取らない関連関数の場合は異なります:

trait Animal {
    fn baby_name() -> String;
}

struct Dog;
struct Cat;

impl Dog {
    fn baby_name() -> String {
        String::from("Spot")
    }
}

impl Animal for Dog {
    fn baby_name() -> String {
        String::from("puppy")
    }
}

impl Animal for Cat {
    fn baby_name() -> String {
        String::from("kitten")
    }
}

Dog::baby_name() // 問題なし
Animal::baby_name() // CatまたはDog?

ここでは、メソッドに対する完全修飾構文が不十分であること(また、コードはコンパイルされません)がわかります。実際には、_完全_修飾構文を使用していなかったからです。以下は上記のコードをコンパイル可能にする完全修飾構文の使用方法です:

<Dog as Animal>::baby_name() // 子犬です
<Cat as Animal>::baby_name() // 子猫です

この構文はメソッドにも使用できますが、非常に冗長です。

スーパートレイト:トレイトの依存関係を宣言する


traitを定義する際に、任意のsupertraitを宣言することができ、それが実装されなければそのtraitは期待通りに動作しません。supertraitを実装していないstructを使用すると、コードのコンパイルが失敗します:

trait PrettyPrint: fmt::Display {
    // 何かする
}

このコードはDisplayを実装するstructs上でのみ実装できるtraitを宣言します。

スーパートレイトは、コードの重複を減らすのを助けることができますが、コードの結合と複雑さを増加させるコストがかかります。

タイプの魔法


これまでtypeキーワードについてほとんど触れていませんでした。このキーワードはタイプエイリアスを宣言するために使用されます:

type Kilometer = i32;

重要なのはKilometerがまだi32であるということです:

let a: Kilometer = 1;
let b = 1;

asserteq!(a + b, 2);

Rustでは newtype パターンがよく見られます。これはRustのtrait実装ルールのために人気があります:

トレイトは、そのトレイト または タイプがあなたのコードにローカルである場合にのみ、タイプに対して実装することができます。

上記のコードをnewtypeパターンを使用して実装できます:

struct Kilometer(i32);

let a = Kilometer(1);
let b = 1;

asserteq!(a + b, 2);

このコードはコンパイルできません。なぜならKilometerAddトレイトを実装していないからです。i32はそのトレイトを実装しており、このことがtypeエイリアスが本当にi32を囲むエイリアスであることの証明です。

エイリアスとnewtypeをいつ使うか?

エイリアスは次の場合に意味があります:

  • 長いタイプ(入れ子になったResultやOptionsなど)を短縮できる場合
  • その一部が常に同じである複合型で、他の部分をジェネリックとして扱うべき場合
  • 既存のタイプ(例えばi32)に意味のある名前を与えることで、コードを読みやすくすることができる場合

_Newtype_は次の場合に意味があります:

  • 外部のトレイトを実装する必要がある場合
  • パブリックAPIを使用して内部コードへのアクセスを制御する場合

あなたが知らなかったタイプ

機能が!を返す場合、いわゆる_never_タイプを返します。_never_タイプはRustの_empty_タイプの別名です。neverタイプのポイントは、戻り値がない関数を表現することです。

このような関数は、他のどのタイプにも強制できる結果を生成します。これは、matchステートメントを使用するときに便利です。これらは常に1つのタイプを返さなければなりません。continuepanic!loopなどのキーワードとマクロはneverタイプを返します。

サイズのあるタイプ

サイズのあるタイプに関する短いメモ。デフォルトでは、ジェネリックな関数は_サイズのある_タイプしか取れません。タイプがコンパイル時にそのサイズがわかっている場合、そのタイプは_サイズのある_ものです。サイズのあるタイプはデフォルトでSizedトレイトを実装しています。

ジェネリックで動的サイズの型を使用したい場合は、?Trait表記を使用する必要があります:

fn generic<T: ?Sized>(t: &T) {}

動的サイズの型も参照を介してヒープ上に保存する必要があるため、動的サイズの型への参照も受け取ることに注意してください。

関数の受け渡しと返戻


fnタイプを使用することで関数を簡単に関数に渡すことができます。これは引数としてclosureが渡されるべきことを示すために使用されるtrait制約と異なります。fn型のものは3つのFnトレイトをすべて実装するため、関数を受け入れる場所では常に関数を渡すことができます。

関数からclosureを返す場合、closureのサイズは実行時に決定されるため、それをBoxでラップする必要があります。

最後に:マクロ


最も高度なRust機能はmacroです。これらはRustコードを使用してさらに多くのRustコードを生成する関数です。このパターンは一般に_metaprogramming_として知られています。

Rustには異なる種類のマクロがあります。これらは異なる実装の詳細を持ち、それぞれが異なるユースケースに適しています。

macro_rules! マクロ

Rustで最も一般的なマクロのタイプは宣言的マクロです。これはmacro_rules! マクロを使用して作成されます。宣言的マクロでは、Rustソースコードをパターンに一致させ、一致するパターンの種類に基づいて異なる種類のコードを生成することができます。これにより、これらのマクロは定義されていない数の引数を取ることができます。

手続き型マクロ

残りの3種類のマクロは手続き型です。これらのマクロはRustソースコードを入力として取り、それを返さなければなりません。

これらの最初のものは_derive_マクロです。deriveマクロを作成するには、標準ライブラリのproc_macro_deriveマクロからderiveする必要があります。Deriveマクロは**structsenums**にのみ機能します。

Deriveマクロはパッケージングに関するいくつかの制約があります。このようなマクロを開発するとき、開発者はそれをメインライブラリの依存関係として含めることを奨励されます。その後、マクロは単にメインライブラリの一部として再エクスポートされ、ユーザーがインポートするために使われます。

Attributeマクロはderiveマクロに似ていますが、関数や他のデータ型などにも使用できるようにもっと柔軟性があります。これらはproc_macro_attributeマクロから派生します。

最後に、関数のようなマクロがあります。手続き的な性質にもかかわらず

こちらの記事はdev.toの良い記事を日本人向けに翻訳しています。
https://dev.to/johnnylarner/30-days-of-rust-day-30-4dan