Open12

Rust:初心者から「なにもわからない」になるために学ぶ

ピン留めされたアイテム
N.KOTANIN.KOTANI

目的

Rustを触ったことがない筆者が、
Rustの知識量を「なにもわからない」まで深める。

資料

  • THE BOOK
    基本的な構文などを学ぶ
  • ChatGPT
    Docker構築などで活用
  • Udemy
    基本的な構文などを学ぶ
N.KOTANIN.KOTANI

環境構築~Hellow World!

  • Dockerで環境構築を行う。
  1. フォルダ構成およびファイルを下記内容で構築する。

    • rust-project
      • docker-compose.yml
      • Dockerfile
      • first_project
        • Cargo.toml
      • src
        • main.rs
  2. ファイルを作成。

    • docker-compose.yml
     services:
         rust_app:
             build: .
             volumes:
               - .:/usr/src/app
             stdin_open: true
             tty: true
    
    • Dockerfile
     FROM rust:latest
     WORKDIR /usr/src/app
     COPY . .
     CMD ["sleep", "infinity"]
    
    • Cargo.toml
     [package]
     name = "first_project"
     version = "0.1.0"
     authors = ["Your Name <you@example.com>"]
     edition = "2018"
     [dependencies]
    
    • main.rs
     fn main(){
         println!("Hello,world");
     }
    
  3. Dockerコマンドを打つ。

     docker-compose build
     docker-compose up -d
     docker-compose exec rust_app bash
     cd first_project
     cargo run
    
N.KOTANIN.KOTANI

Cargoとは?

  • Rustのビルドシステム、およびパッケージマネージャー。
  • cargoはコマンドが存在し、実行可能ファイル、プロジェクトの作成や、テストや実行を行うことができる。

Cargo Command(抜粋)

コマンド名 処理内容
help Cargoについてのヘルプを表示
check コードのエラーをチェックする
build プロジェクトをビルドし、実行ファイルを生成する
run プロジェクトをビルドし、実行する

Cargo.toml

Cargo.tomlとは、Rustにおける設定ファイルで、
依存関係、ビルド設定を定義する。

  • Cargo.toml
[package]
name = "input_project"
version = "0.1.0"
edition = "2021"

[dependencies]

「package」
プロジェクトのメタデータを定義する。

  • name:パッケージの名前
  • version:パッケージのバージョン
  • edition:使用する Rust のエディション

「dependencies」
プロジェクトが依存する外部クレートを定義します。

N.KOTANIN.KOTANI

変数

  • 変数は、デフォルトでは不変(immutable)になる
  • 可変にする場合は、明示的にmutableを設定する必要がある
fn main() {
    // immutable
    let im_number = 0;

    // mutable
    let mut mu_number = 10;

    println!("{}",im_number);
    println!("{}",mu_number);
}

  • 変数代入でエラーが出る
fn main() {
    let im_number = 0;
    im_number = 10;
    println!("{}",im_number);
}
N.KOTANIN.KOTANI

String::from

  • Rustの標準ライブラリ関数
  • 文字列スライス(&str)を所有する文字列(String)に変換するために使用されます
pub fn run(){
    let mut s1 = String::from("hello");
    s1.push_str("_new1");
    println!("{}",s1);
}
N.KOTANIN.KOTANI

配列とベクタ型

  • 配列は固定長のサイズなので、要素を追加することができない
  • ベクタ型は、可変長サイズなので、要素を追加することができる
pub fn run(){

    //配列
    let nv1 = [1,2,3,4,5];
    println!("{:?},{:?}",nv1);

    //ベクタ型
    let mut v1 = vec![1,2,3];
    let v2 = vec![1,2,3];
    let mut v3 = vec![9,10];

    println!("{:?}",v1);

    //データを追加
    v1.insert(1,10);
    println!("{:?}",v1);

    //要素を削除
    v1.remove(0);
    println!("{:?}",v1);

    //他のデータを挿入する
    v1.append(&mut v3);
    println!("{:?}",v1);
    println!("{:?}",v3);
}
N.KOTANIN.KOTANI

BOX

Rustにおける全ての値は、デフォルトでスタックに格納される。
ボックス化することで、値をヒープに割り当てすることができる。

pub fn run(){
    //box型を宣言する
    let mut b1 = Box::new(t1);
    
    //参照はずしをして、データを変更することができる
    (*b1).1 += "world!";
    println!("{} {}",b1.0,b1.1);
}
N.KOTANIN.KOTANI

ジェネリクス,trait境界

  • ジェネリクス
    • 型や関数に対して汎用的なコードを書くことを可能にする機能
    • ジェネリクスを使用することで、同じコードを異なる型で再利用可能になる
  • Trait境界
    • ジェネリクスで使用する型が特定のトレイトを実装していることを制約するために使用
    • ジェネリクス関数や構造体が必要なメソッドや機能を型に対して利用できるようになります。
pub fn run(){
    let number_list = vec![34,50,25,100,65];
    println!("{}",largest_method(number_list));

    let chr_list = vec!['a','b','c','d'];
    println!("{}",largest_method(chr_list));
}

//generics型にして、汎用的なメソッドを作成する
// fn 〇〇<T>{~}
//比較をする際、trait境界を設定する必要がある
// fn 〇〇<T:PartialOrd + Copy>{~}
fn largest_method<T:PartialOrd + Copy>(list:Vec<T>) -> T{
    let mut largest = list[0];
    for item in list{
        if item > largest {
            largest = item;
        }
    }
    largest
}

N.KOTANIN.KOTANI

構造体

  • 構造体は、sructで定義をする
  • 構造体は定義をした後、インスタンスを生成して、使用をする

impl

  • 構造体や列挙型に関連するメソッドを定義することができる
  • selfやotherで変数を定義することができる
    • self:インスタンスの所有権をメソッドに渡す場合に使用する
    • other;別のインスタンス
//ジェネリック型の構造体を作成
struct Point<T>{
    x:T,
    y:T
}

//ジェネリック型で、別々のデータ型を設定したいときは、複数設定する必要がある
struct APoint<T,U>{
    x:T,
    y:U
}

impl<T,U> APoint<T,U>{
    fn mixedup<V,W>(self,other:APoint<V,W>) -> APoint<T,W>{
        APoint{
            x:self.x,
            y:other.y
        }
    }
}

pub fn run(){
    //構造体のインスタンスを作成する
    let s_point = Point{x:1,y:2};
    let s_pont2 = Point{x:1.0,y:2.0};

    let point1 = APoint{x:5,y:10.4};
    let point2 = APoint{x:"Rust",y:'a'};
    let point3 = point1.mixedup(point2);
    println!("{} {}",point3.x,point3.y);
}
N.KOTANIN.KOTANI

列挙型

  • Rustで列挙型を使う際は、定義とインスタンス生成を行います。
//列挙型を定義する
// バリアントとデータの型を定義する
enum Coffee{
    Kilimanjaro(String,u32),
    BlueMountain(String,u32),
    Guatemala(String,u32)
}

pub fn run(){
    //インスタンスを作成する
    let kilimanjaro = Coffee::Kilimanjaro(String::from("タンザニア"),1890);
    print_info(kilimanjaro);
    
    let bluemountain = Coffee::BlueMountain(String::from("ジャマイカ"),1930);
    print_info(bluemountain);
    
    let guatemala = Coffee::Guatemala(String::from("グアテマラ"),1750);
    print_info(guatemala);
}

fn print_info(coffee:Coffee){
//Rustでは列挙型を使ってのパターンマッチが一般的で、各バリアントごとに処理を分けるように使います
    match coffee{
        Coffee::Kilimanjaro(country,year) => {
            println!("キリマンジャロは、原産国は{}で、{}年代が起源とされている ",country,year);
        }
        Coffee::BlueMountain(country,year) => {
            println!("ブルーマウンテンは、原産国は{}で、{}年代が起源とされている ",country,year);
        }
        Coffee::Guatemala(country,year) => {
            println!("グアテマラは、原産国は{}で、{}年代が起源とされている ",country,year);
        }
    }
}
N.KOTANIN.KOTANI

Trait

  • Traitとは、特定の型に存在し、他の型と共有できる機能。
    トレイトを使用すると、共通の振る舞いを抽象的に定義ができる。
  1. traitを定義する
    デフォルトで関数の処理を定義することもできる。
  2. 構造体や列挙型に実装をする
  3. インスタンス生成を行う
  4. 引数で「impl trait名」で、trait型の引数を取得できるメソッドを定義することができる。
//1
trait Coffee{
    fn price(&self)->u32;
    fn features(&self)->String;
    fn printOwner(&self){
        println!("生産者は田中です");
    }
}

//2
struct Kilimanjaro{
    price:u32,
    features:String,
}
impl Coffee for Kilimanjaro{
    fn price(&self)->u32{
        self.price
    }
    fn features(&self)->String{
        format!("{}",self.features)
    }
}

//2
struct BlueMountain{
    price:u32,
    features:String
}
impl Coffee for BlueMountain{
    fn price(&self)->u32{
        self.price + 10
    }
    fn features(&self)->String{
        format!("{}ですが、とても美味しい",self.features)
    }
}

pub fn run(){
    //3
    let kilimanjaro = Kilimanjaro{
        price:9000,
        features:String::from("とても甘い")
    };
    println!("お値段は{}",kilimanjaro.price());
    println!("特徴は{}",kilimanjaro.features());
    kilimanjaro.printOwner();    
    coffeeInfo(&kilimanjaro);
    
    //3
    let bluemountain = BlueMountain{
        price:1000,
        features:String::from("とても甘い")
    };
    println!("お値段は{}",bluemountain.price());
    println!("特徴は{}",bluemountain.features());
    coffeeInfo(&bluemountain);

}

//4
fn coffeeInfo(item:&impl Coffee){
    println!("値段:{} 特徴:{}",item.price(),item.features());
    item.printOwner();
}
N.KOTANIN.KOTANI

Unit Test

  • テストモジュールを定義をする際は、テスト対象のモジュールと同じファイルに記述する
  • テスト用のモジュールには#[cfg(test)]アトリビュートを付け、テスト用メソッドに#[test]を付けることでテストを実装することができる。
  • また、テストモジュールは、テスト対象のモジュールよりも一つ下の階層にあるので、値を取り込む際は「use」を利用する
pub fn add_one(x:i32,y:i32)->i32{
    x + y
}

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

    #[test]
    fn test_failur(){
        let one = add_one(10,10);
        assert_eq!(one, 200);
    }

    #[test]
    fn test_success(){
        let one = add_one(10,10);
        assert_eq!(one, 20);
    }
}