Closed16

[Rust] 入門覚書

mosumosu

スライスって何に使うんだろう

文字列の一部を参照したくなる可能性があるのと同様、配列の一部を参照したくなる可能性もあります。 以下のようにすれば、参照することができます:

fn main() {
  let a = [1, 2, 3, 4, 5];
  let slice = &a[1..3];
}

部分的に参照したい時に使う感じか?
とりあえず存在は覚えておくけど、実際に出てくるまではワカラン

mosumosu

フィールドのないユニット様よう構造体
また、一切フィールドのない構造体を定義することもできます!これらは、()、ユニット型と似たような振る舞いをすることから、 ユニット様構造体と呼ばれます。ユニット様構造体は、ある型にトレイトを実装するけれども、 型自体に保持させるデータは一切ない場面に有効になります。トレイトについては第10章で議論します。

struct Unit {}

fn main() {
    let unit = Unit {};
}

トレイトが分かるまで、謎

mosumosu

10章まで待てないので、チラ見

トレイトは、Rustコンパイラに、特定の型に存在し、他の型と共有できる機能について知らせます。 トレイトを使用すると、共通の振る舞いを抽象的に定義できます。トレイト境界を使用すると、 あるジェネリックが、特定の振る舞いをもつあらゆる型になり得ることを指定できます。

trait GameObject {
    fn render(&self) -> bool;
}

struct Human {
    hp: u8,
    mp: u8,
    position: (u32, u32),
}
struct Wall {
    position: (u32, u32),
}

struct UnitItem {}

impl GameObject for Human {
    fn render(&self) -> bool {
        if self.hp > 10 && self.mp > 10 {
            println!("Human Position: {}, {}", self.position.0, self.position.1);
        }
        return true;
    }
}

impl GameObject for Wall {
    fn render(&self) -> bool {
        println!("Wall Position: {}, {}", self.position.0, self.position.1);
        return true;
    }
}

impl GameObject for UnitItem {
    fn render(&self) -> bool {
        println!("HogeHoge");
        return true;
    }
}

fn main() {
    let wall = Wall { position: (0, 10) };
    wall.render();

    let human = Human {
        hp: 100,
        mp: 100,
        position: (10, 20),
    };
    human.render();

    let unit = UnitItem {};
    unit.render();
}

output
    Finished dev [unoptimized + debuginfo] target(s) in 0.01s
     Running `target\debug\sandbox.exe`
Wall Position: 0, 10
Human Position: 10, 20
HogeHoge

フィールドは要らんけど、traitで書いといた関数だけ実装させたい みたいな感じ?
使い道はまだ分からんけど・・・

mosumosu

printlnで出すにはuse std::fmt::Debugを実装するか #[derive(Debug)]をつける

#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

fn main() {
    let rect1 = Rectangle {
        width: 30,
        height: 50,
    };
    println!("rect1 is {:#?}", rect1);
}
output
    Finished dev [unoptimized + debuginfo] target(s) in 0.02s
     Running `target\debug\sandbox.exe`
rect1 is Rectangle {
    width: 30,
    height: 50,
}

OR

use std::fmt::Debug;

struct Rect {
    h: u32,
    w: u32,
}

impl Debug for Rect {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("[Rect]")
            .field("height", &self.h)
            .field("width", &self.w)
            .finish()
    }
}

fn main() {
    let rect = Rect { h: 1920, w: 1080 };
    println!("Rect -> {:?}", rect);
}
output
    Finished dev [unoptimized + debuginfo] target(s) in 0.28s
     Running `target\debug\sandbox.exe`
Rect -> [Rect] { height: 1920, width: 1080 }
mosumosu
#[derive(Debug)]
struct Rectangle {
    width: u8,
    height: u8,
}

impl Rectangle {
    fn area(&self) -> u32 {
        return u32::from(self.width) * u32::from(self.height);
    }
}

fn main() {
    let rect1 = Rectangle {
        width: 255,
        height: 255,
    };
    println!("{}", rect1.area());
}

定義はu8で、計算結果がu8超える場合は、計算時にキャストするのかな?
定義をu32にするのは勿体ないとかあるのだろうか。

mosumosu

VSCodeで型書いてないときに、自動で表示されるの便利なんだけど、横に長くなるので以下を設定。
"editor.inlayHints.enabled": "offUnlessPressed"
Ctrl + Altで表示できるのアツイ

mosumosu

文字列をピッグ・ラテン(訳注: 英語の言葉遊びの一つ)に変換してください。各単語の最初の子音は、 単語の終端に移り、"ay"が足されます。従って、"first"は"irst-fay"になります。ただし、 母音で始まる単語には、お尻に"hay"が付け足されます("apple"は"apple-hay"になります)。 UTF-8エンコードに関する詳細を心に留めておいてください!

fn main() {
    assert_eq!("english-hay", task("english"));
    assert_eq!("irst-fay", task("first"));
    assert_eq!("apple-hay", task("apple"));
}

fn task(input: &str) -> String {
    let mut output = String::new();
    match &input[..1] {
        "a" | "i" | "u" | "e" | "o" => {
            output.push_str(&input);
            output.push_str("-hay");
        }
        _ => {
            output.push_str(&input[1..]);
            output.push_str("-");
            output.push_str(&input[..1]);
            output.push_str("ay");
        }
    }
    return output;
}

そのまんま書いたけど、絶対もっとRustっぽくかけるはず…

mosumosu

糖衣構文

  • トレイト境界(trait bound)
  • impl trait
pub fn notify<T: Summary>(item: &T) {
    println!("Breaking news! {}", item.summarize());
}
pub fn notify(item: &impl Summary) {
    println!("Breaking news! {}", item.summarize());
}
mosumosu

ただし、impl Traitは一種類の型を返す場合にのみ使えます。 たとえば、以下のように、戻り値の型はimpl Summaryで指定しつつ、NewsArticleかTweetを返すようなコードは失敗します:

fn returns_summarizable(switch: bool) -> impl Summary {
    if switch {
        NewsArticle {
            headline: String::from(
                "Penguins win the Stanley Cup Championship!",
            ),
            location: String::from("Pittsburgh, PA, USA"),
            author: String::from("Iceburgh"),
            content: String::from(
                "The Pittsburgh Penguins once again are the best \
                 hockey team in the NHL.",
            ),
        }
    } else {
        Tweet {
            username: String::from("horse_ebooks"),
            content: String::from(
                "of course, as you probably already know, people",
            ),
            reply: false,
            retweet: false,
        }
    }
}

じゃあどうするのか?
多分こう

fn returns_summarizable(switch: bool) -> Box<dyn Summary> {
    if switch {
        Box::new(NewsArticle {
            headline: String::from("Penguins win the Stanley Cup Championship!"),
            location: String::from("Pittsburgh, PA, USA"),
            author: String::from("Iceburgh"),
            content: String::from(
                "The Pittsburgh Penguins once again are the best \
                 hockey team in the NHL.",
            ),
        })
    } else {
        Box::new(Tweet {
            username: String::from("horse_ebooks"),
            content: String::from("of course, as you probably already know, people"),
            reply: false,
            retweet: false,
        })
    }
}
mosumosu
fn largest<T: PartialOrd + Copy>(list: &[T]) -> T {
    let mut largest = list[0];
    for &item in list {
        if item > largest {
            largest = item;
        }
    }
    largest
}

largestの別の実装方法は、関数がスライスのT値への参照を返すようにすることです。 戻り値の型をTではなく&Tに変え、それにより関数の本体を参照を返すように変更したら、 CloneやCopyトレイト境界は必要なくなり、ヒープ確保も避けられるでしょう。 これらの代替策をご自身で実装してみましょう!

fn largest<T: PartialOrd>(list: &[T]) -> &T {
    let mut largest = &list[0];
    for item in list {
        if item > largest {
            largest = item;
        }
    }
    largest
}
mosumosu

ライフタイム注釈

昔は1引数でも必要だった
fn first_word<'a>(s: &'a str) -> &'a str {

3つの規則を試して、ライフタイムを計算できる場合、省略可能

  1. 参照である各引数は、独自のライフタイム引数を得る
  2. 1つだけ入力ライフタイム引数があるなら、そのライフタイムが全ての出力ライフタイム引数に代入される
  3. 複数の入力ライフタイム引数があるけれども、メソッドなのでそのうちの一つが&selfや&mut selfだったら、 selfのライフタイムが全出力ライフタイム引数に代入される
mosumosu

テストでは成功時のprintln!は表示されない
→ cargo test -- --nocapture

テストを並列で実行しない
→ cargo test -- --test-threads=1

mosumosu

単体テスト

各モジュール内で実施

src/lib.rs
pub fn add10(x: u32) -> u32 {
    x + 10
}

#[cfg(test)]
mod test {
    use super::*;
    #[test]
    fn test_add10() {
        assert_eq!(15, add10(5))
    }
}

結合テスト

srcと同階層にtestsディレクトリを配置

tests/itg_test.rs
extern crate sandbox;

#[test]
fn test_add10() {
    assert_eq!(20, sandbox::add10(10));
}
このスクラップは2023/02/22にクローズされました