1
0
お題は不問!Qiita Engineer Festa 2024で記事投稿!
Qiita Engineer Festa20242024年7月17日まで開催中!

Rust 100 Ex 🏃【23/37】 `impl Trait` ・スライス ~配列の欠片~

Last updated at Posted at 2024-07-17

前の記事

全記事一覧

100 Exercise To Learn Rust 演習第23回になります!

今回の関連ページ

[06_ticket_management/09_impl_trait_2] impl Trait 2 (引数位置の impl Trait )

問題はこちらです。

lib.rs
// TODO: Rework the signature of `TicketStore::add_ticket` to use a generic type parameter rather
//  than `impl Trait` syntax.

use ticket_fields::{TicketDescription, TicketTitle};

#[derive(Clone)]
pub struct TicketStore {
    tickets: Vec<Ticket>,
}

#[derive(Clone, Debug, PartialEq)]
pub struct Ticket {
    pub title: TicketTitle,
    pub description: TicketDescription,
    pub status: Status,
}

#[derive(Clone, Debug, Copy, PartialEq)]
pub enum Status {
    ToDo,
    InProgress,
    Done,
}

impl TicketStore {
    pub fn new() -> Self {
        Self {
            tickets: Vec::new(),
        }
    }

    // Using `Into<Ticket>` as the type parameter for `ticket` allows the method to accept any type
    // that can be infallibly converted into a `Ticket`.
    // This can make it nicer to use the method, as it removes the syntax noise of `.into()`
    // from the calling site. It can worsen the quality of the compiler error messages, though.
    pub fn add_ticket(&mut self, ticket: impl Into<Ticket>) {
        self.tickets.push(ticket.into());
    }
}
テストを含めた全体
lib.rs
// TODO: Rework the signature of `TicketStore::add_ticket` to use a generic type parameter rather
//  than `impl Trait` syntax.

use ticket_fields::{TicketDescription, TicketTitle};

#[derive(Clone)]
pub struct TicketStore {
    tickets: Vec<Ticket>,
}

#[derive(Clone, Debug, PartialEq)]
pub struct Ticket {
    pub title: TicketTitle,
    pub description: TicketDescription,
    pub status: Status,
}

#[derive(Clone, Debug, Copy, PartialEq)]
pub enum Status {
    ToDo,
    InProgress,
    Done,
}

impl TicketStore {
    pub fn new() -> Self {
        Self {
            tickets: Vec::new(),
        }
    }

    // Using `Into<Ticket>` as the type parameter for `ticket` allows the method to accept any type
    // that can be infallibly converted into a `Ticket`.
    // This can make it nicer to use the method, as it removes the syntax noise of `.into()`
    // from the calling site. It can worsen the quality of the compiler error messages, though.
    pub fn add_ticket(&mut self, ticket: impl Into<Ticket>) {
        self.tickets.push(ticket.into());
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use ticket_fields::test_helpers::{ticket_description, ticket_title};

    struct TicketDraft {
        pub title: TicketTitle,
        pub description: TicketDescription,
    }

    impl From<TicketDraft> for Ticket {
        fn from(draft: TicketDraft) -> Self {
            Self {
                title: draft.title,
                description: draft.description,
                status: Status::ToDo,
            }
        }
    }

    #[test]
    fn generic_add() {
        let mut store = TicketStore::new();
        // This won't compile if `add_ticket` uses `impl Trait` syntax in argument position.
        store.add_ticket::<TicketDraft>(TicketDraft {
            title: ticket_title(),
            description: ticket_description(),
        });
    }
}

前回RPITとして熱く語った impl Trait という表現を、ジェネリクスに直してください、という問題です!

解説

引数位置にある impl Trait はRPITではなく、どちらかというと(ほぼ)ジェネリクスと同じ意味を持ちます。よって、ジェネリクスで書いてからトレイト境界を書けばよいです!

lib.rs
impl TicketStore {
    pub fn add_ticket<T>(&mut self, ticket: T)
    where
        T: Into<Ticket>,
    {
        self.tickets.push(ticket.into());
    }
}

今回は丁寧に where を使ってみました。 Book の方にも書いていますが、ジェネリクスと「ほぼ」同じ意味とした「ほぼ」の部分の違いについて、 impl Trait だと例えばターボフィッシュ構文 ( ::<T> )による型指定ができません 。ターボフィッシュ構文というのは、呼び出される関数が曖昧になってしまわないように、ジェネリックスパラメータを具体的に指定するための構文です1。本問題はそれを確認する問題でした!

ちなみに本問題はテストのターボフィッシュ構文側を単に取り除いてしまえばコンパイルが通ります(テストを書き換えるのは回答としてはダメですが、つまりターボフィッシュの必要性を説明できていないということです)...ターボフィッシュ構文じゃないと辛くなる例を見つけようと色々試していたのですが、ターボフィッシュの問題以前にジェネリクス引数の方が表現力が強く、引数位置 impl Trait だとそもそもいい感じに書けない場合が多そうでした。例えば、引数と返り値で同じ型にしたければジェネリック引数 T を使う他ありません。

もし引数位置 impl Trait で書ける処理ならば多分特に困らないです。ジェネリックパラメータをタイプするのすら面倒な時にはそこそこ便利ですので、使ってもいいんじゃないかと個人的には思います。

[06_ticket_management/10_slices] スライス

問題はこちらです。

lib.rs
// TODO: Define a function named `sum` that takes a reference to a slice of `u32` and returns the sum of all
//  elements in the slice.

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn empty() {
        let v = vec![];
        assert_eq!(sum(&v), 0);
    }

    #[test]
    fn one_element() {
        let v = vec![1];
        assert_eq!(sum(&v), 1);
    }

    #[test]
    fn multiple_elements() {
        let v = vec![1, 2, 3, 4, 5];
        assert_eq!(sum(&v), 15);
    }

    #[test]
    fn array_slice() {
        let v = [1, 2, 3, 4, 5];
        assert_eq!(sum(&v), 15);
    }
}

解説

早速答えです、スライスが持つメソッド iter を呼び出しているだけでシンプルです!

lib.rs
fn sum(array: &[u32]) -> u32 {
    array.iter().sum()
}

String についてその参照として特化した &str があったように、Vec や配列に対して特化した型であるスライス &[T] が登場しました。

関数にてスライスとして型を宣言することで、「スタック上の静的配列 [T; N] の参照」、「ヒープ上の動的配列 Vec<T> の参照」両方を受け取れるようになります!

Rust
fn main() {
    println!("{}", sum(vec![0, 1, 2, 3].as_slice()));
    println!("{}", sum(    [4, 5, 6, 7].as_slice()));
}

String&'static str (文字列リテラル) の両方を受け取れる &str と同様、配列や動的配列を不変参照として受け取る際はスライス &[T] として受け取ると良さそうです。

では次の問題に行きましょう!

次の記事: 【24】 可変スライス・下書き構造体 ~構造体で状態表現~

  1. 自分がそうだったことからの憶測ですが、おそらく多くのRustaceanが初めてターボフィッシュを知るのはコンパイルエラー経由なんじゃないかと思います。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0