Published on

Rust基礎

Authors
  • avatar
    Name
    Kikusan
    Twitter

The Rust Programming Language 日本語版
に沿って1枚ペラで殴り書きしていきます。

VSCode拡張機能

  • rust-analyzer
  • codeLLDB

変数とデータ型

https://doc.rust-jp.rs/book-ja/ch03-01-variables-and-mutability.html
https://doc.rust-jp.rs/book-ja/ch03-02-data-types.html

let x: i32 = 5; // let 変数名: 型 = 初期値;
let _x = 5; // 型は推論でき、_をつけると非参照警告を消せる

let x = 10; // 再度宣言すると前の宣言は隠される(シャドーイング)
{
   let x = 20;
   println!("{}", x); // 20  
}
println!("{}", x); // 10 シャドーイングはスコープ内でのみ有効

const MAX_POINTS: u32 = 100_000; // 定数 定数だけは関数外に定義できる

let mut y = 5; // mutは代入化を示す

// &でポインタを取得 ポインタを参照しても代入されるのは値になるが、:pによってコンソールでポインタアドレスが見られる
println!("{}", MAX_POINTS); // 100000
println!("{}", &MAX_POINTS); // 100000
println!("{:p}", &MAX_POINTS); // 0x1026958d0 定数はstatic領域に格納される
println!("{:p}", &x); // 0x16bd2a7b4 固定長変数はstack領域に格納される

タプル

let t1 = (500, 6.4, "dummy"); // tupleは(i32, f64, &str)のように型定義できる
let (_x, _y, _z) = t1; // 展開可能
println!("{}, {}, {}", t1.0, t1.1, t1.2); // インデックスアクセス

let mut t2 = ((0, 1), (2, 3));
// refによって参照渡しができる (&mut i32型) &mut はmutableポインタ
let ((ref mut x1_ptr, mut y1_ptr), _) = t2;
*x1_ptr = 4; // 6mutで渡した変数の代入には*が必要
y1_ptr = 5;
println!("{}", x1_ptr); // 4
println!("{}", y1_ptr); // 5
// 参照渡ししたほうしか変わらない
println!("{:?}", t2); // ((4, 1), (2, 3)) :?によって複雑な要素も出力できる.
println!("{:p}", x1_ptr); // 0x16f89a780
println!("{:p}", &t2.0 .0); // 0x16f89a780 // ポインタ渡しのためアドレス同じ

配列

let a1 = [1, 2, 3, 4, 5]; // 型は[i32; 5] 後ろは要素数
let a2 = [0; 10]; // ;の前の値で要素が埋められる

println!("{:?}", a2); // [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
println!("{}", a1[4]); // 5

println!("{:?}", &a1[..3]); // [1, 2, 3] &配列[]でスライスを取得できる

文字列

let s1 = "こんにちは"; // 文字列スライス型は&str utf-8が採用されている
let s2 = "hello";
println!("{:p}", &s1); // 0x16ceb68c0
println!("{:p}", &s2); // 0x16ceb68d0 // stack上で16bytesのズレ
                     // 実態のポインタ位置の保存に8byte, byte数の保存に8byte使用しており、文字列の実態は静的領域に保存される

println!("{:?}", s1.as_ptr()); // 0x1005f8e46 実態のアドレス
println!("{}", s1.len()); // 15 byte数

let mut st3 = String::from("hello"); // String型
let st4 = String::from("world");

println!("{:p}", &st3); // 0x16b7de890
println!("{:p}", &st4); // 0x16b7de8a8 24bytesのズレ
                        // 文字列スライス型と同じ16byteに加え、予測される最大byte数を8byteで保存している 文字列の実態はheapに格納される

println!("{:?}", st3.as_ptr()); // 0x140606b60 実態のアドレス
println!("{}", st3.len()); // 5 文字長
println!("{}", st3.capacity()); // 5 最大文字長 String::with_capacity()で前もって容量を確保して後から動的割り当てをさせないときに使用する

st3.push_str(&st4); // String型は基本的に演算不可の&str型と違いメソッドがある
println!("{}", st3); // helloworld

let _st5: String = s2.to_string(); // &strはto_stringでString型に変換できる
let helloworld: &str = &st3; // &String型は&str型に変換できる
println!("{}", &helloworld[0..5]); // hello &&str型にはスライスできる

ベクタ型

let mut v1 = vec![1, 2, 3, 4];
let v2 = vec![5, 6, 7, 8];

println!("vector: {:p}", &v1); // 0x16b0fe8b0
println!("vector: {:p}", &v2); // 0x16b0fe8c8 24byteのズレ. Stringと一緒で、8byteポインタ, 8byte len, 8byte capacityになっている.

// vector型は動的に要素数を変更できる
println!("vector: {:?}", v1); // [1, 2, 3, 4]
v1.insert(1, 10); // (index, element)
v1.insert(1, 10);
v1.remove(0);
v1.sort(); // ソート
v1.dedup(); // 重複排除
println!("vector: {:?}", v1); // [2, 3, 4, 10]

Boxポインタ

// https://doc.rust-jp.rs/book-ja/ch15-01-box.html
    let t1: (i64, String) = (10, String::from("hello"));
    println!("Stack address of tuple data is: {:p}", &t1); // 0x16b02a8a8
    println!("Heap memory address of t1.1: {:?}", t1.1.as_ptr()); // 0x120e06b60

    let mut b1 = Box::new(t1); // Box Pointerの作成 stackに存在していたtupleはheapに移動する stackにboxpointerが入る
    (*b1).1 += "world"; // 参照外しで実体にアクセス

    println!("heap address of tuple data is: {:p}", b1); // 0x146606b80 移動したtupleのアドレス
    println!("{}, {}", b1.0, b1.1); // 10, helloworld  tupleの参照先は残る

    enum List {
        // enumはコンパイル時に最大メモリサイズを確認するので、再帰にしているとエラーが出る
        Node(i32, Box<List>), // ジェネリクス BoxPointerを使用して再帰を回避
        Nil,
    }
    let _list = List::Node(1, Box::new(List::Node(2, Box::new(List::Nil))));

構文

if

let number = 6;

if number % 4 == 0 {
   println!("number is divisible by 4");
} else if number % 3 == 0 {
   println!("number is divisible by 3");
} else if number % 2 == 0 {
   println!("number is divisible by 2");
} else {
   println!("number is not divisible by 4, 3, or 2");
}

let condition = true;
let number = if condition { 5 } else { 6 }; // ifは式である 戻す型は同一でなければならない

println!("The value of number is: {number}");

// number is divisible by 3
// The value of number is: 5

loop

// loopによって繰り返し
let mut count = 0;
'counting_up: loop {
   // loopにはラベルを'~:でつけられる
   println!("count = {count}");
   let mut remaining = 10;

   loop {
      println!("remaining = {remaining}");
      if remaining == 9 {
            break; // loopを抜ける
      }
      if count == 2 {
            break 'counting_up; // ラベルまでループを抜ける
      }
      remaining -= 1;
   }

   count += 1;
}
println!("End count = {count}");

// count = 0
// remaining = 10
// remaining = 9
// count = 1
// remaining = 10
// remaining = 9
// count = 2
// remaining = 10
// End count = 2

while

let mut number = 3;

while number != 0 {
   println!("{number}!");

   number -= 1;
}

println!("LIFTOFF!!!");

for

let a = [10, 20, 30, 40, 50];

// forはコレクションを使用できる(ベクター, 文字列, ハッシュマップ, タプル, 配列)
for element in a {
   println!("the value is: {element}");
}

// the value is: 10
// the value is: 20
// the value is: 30
// the value is: 40
// the value is: 50

for number in (1..4).rev() {
   println!("{number}!");
}
println!("LIFTOFF!!!");

// 3!
// 2!
// 1!
// LIFTOFF!!!

関数

/* ドキュメンテーションコメントは /// で。 markdownが使える
 * ```を使用することで`cargo test`によってdoctestができる
 */
fn run() {
    println!("{}", calc(1, 2)); // 4
}

/// 関数は戻り値がある場合に式で終わる一連のstatement
///
/// # note
///
/// - statement: アクションを実行し値を返さない命令
/// - expression(式): 値を返す命令
/// - (引数: 型) -> 戻り値型
/// ```
/// assert_eq!(rust_lesson::syntax::functions::calc(1, 2), 4);
/// ```
pub fn calc(x: i32, y: i32) -> i32 {
    let n = {
        let m = 2;
        x * m // スコープブロックから値を返す(式)
    };
    y * n
}

構造体

https://doc.rust-jp.rs/book-ja/ch05-01-defining-structs.html

/// structはインスタンスに所有権が発生する
struct User {
    username: String,
    email: String,
    sign_in_count: u64,
    active: bool,
}

/// 同じ名前でimplを使用することで、関数を実装できる
/// 複数回implを宣言もできる
impl User {
    fn create(username: String, email: String) -> Self {
        Self {
            username,
            email,
            sign_in_count: 0,
            active: true,
        }
    }

    fn to_len(&self) -> usize {
        self.username.len() + self.email.len()
    }
}

fn structs() {
    let email = String::from("someone@example.com");
    let mut user1 = User {
        email, // 省略記法
        username: String::from("someusername123"),
        active: true,
        sign_in_count: 1,
    };
    user1.email = String::from("anotheremail@example.com");

    let user2 = User {
        username: String::from("anotherusername567"),
        ..user1 // 構造体の一部を参照して新しいインスタンス
    };

    println!("{}", user2.to_len()); // 42  // インスタンスから関数呼び出し

    let user3 = User::create(user2.username, user2.email); // 直接関数実行ができる

    println!("{}", user3.to_len()); // 42
}

Enum

/// 要素は自由に属性を持たせられる match(switch)に使える
enum Fruits {
   Apple,
    Banana(u32, String),
    Orange(i64),
}

fn fruits() {
   let apple = Fruits::Apple;
    let banana = Fruits::Banana(1, String::from("ばなな"));
    print_fruits_name(banana);
}

fn print_fruits_name(fruits: Fruits) {
   match fruits {
      Fruits::Apple => {
         println!("Apple!");
        }
        Fruits::Banana(n, s) => {
           println!("{}, {}!", n, s);
        }
        Fruits::Orange(n) => {
           println!("Orange!");
        }
    }
}

所有権

https://doc.rust-jp.rs/book-ja/ch04-01-what-is-ownership.html

所有権のルール

  • Rustの各値は、所有者と呼ばれる変数と対応している。
  • いかなる時も所有者は一つである。
  • 所有者がスコープから外れたら、値は破棄される。

rustのメモリ領域は4つに分類される

  • Heap: 可変長データ: String, Vectorなど. ポインタでデータは扱われ、アクセスは遅くなる
  • Stack: サイズが決まった変数や配列など. 高速アクセスできる
  • Static: 静的領域のこと.constや文字列リテラルの実体など.
  • Text: 機械語のコード.

スコープを抜けると自動でdropが呼ばれ、変数で使用していたメモリは解放される

/// ムーブはshallow copy + 元変数の無効化のことで所有者の移動が行われる
fn move_fn() {
    fn move_inner(mut x: String) -> String {
        x.push_str("!");
        println!("{:p}", x.as_ptr()); // 0x11e606b70
        x
    }

    let s = String::from("hello");

    let x = 5;
    let y = x; // ムーブに対してコピー可能な型を代入することをCopyという。

    println!("{:p}", &x); // 0x16d0a68a8
    println!("{:p}", &y); // 0x16d0a68ac 固定長データはそれぞれメモリが割り当てられ、相互依存しない

    println!("{:p}", s.as_ptr()); // 0x126606b60

    let s2 = s; // heapデータのムーブ

    // println!("{:p}", s.as_ptr()); // moved valueは無効化される
    println!("{:p}", s2.as_ptr()); // 0x126606b60 sはs2にムーブされた

    let s3 = s2.clone(); // deep copyする場合はcloneが使える

    println!("{:p}", s2.as_ptr()); // 0x12ce06b60
    println!("{:p}", s3.as_ptr()); // 0x12ce06b70 // heap上のポインタアドレスが異なる

    let s4 = move_inner(s3);

    // println!("{}", s3); // 関数に渡した時もムーブやコピーされる
    println!("{}", s4); // hello!
    println!("{:p}", s4.as_ptr()); // 0x13ce06b70
}

/// 参照では、&によってポインタを渡す。
/// これによってムーブが行われず、関数を呼んだ後も引数の値を使用できる。
fn references() {
    fn references_inner(s: &String) -> usize {
        s.len()
    }

    fn references_mut_inner(s: &mut String) {
        s.push_str("!");
    }

    // 内部の参照を外部に渡すような関数は作成できない(スコープが閉じてメモリ解放された後の参照を残すことになるから)
    // fn references_dangle_inner() -> &String {
    //     let s = String::from("hello");
    //     &s
    // }

    let s1 = String::from("hello");

    let len = references_inner(&s1);

    println!("The length of '{}' is {}.", s1, len);

    let mut s2 = s1.clone();

    references_mut_inner(&mut s2); // (&mut s2) 変数を渡す
    references_mut_inner(&mut s2); // (&mut s2) 変数を渡す
    println!("{}", s2); // hello!!

    let _r1 = &mut s2;
    // let _r2 = &s2; // 可変参照と普遍参照は同時にできない
    // let _r2 = &mut s2;   // &mutの参照は1スコープ/1変数につき1つしか変数として持てない
    // println!("{}, {}", _r1, _r2);
}

ジェネリクス

https://doc.rust-jp.rs/book-ja/ch10-00-generics.html

pub fn run() {
    generics_fn();
    lifetime_generics();
}

fn generics_fn() {
    /// generics function<Generics [:Trait]>
    /// ジェネリクスは他の言語と同じ コンパイラは使用されている箇所を見てコンパイル時点で型を決める
    /// Traitを設定することで使用できる型を制限している
    fn max<T: PartialOrd + Copy>(list: &Vec<T>) -> T {
        let mut largest = list[0];
        for item in list {
            if *item > largest {
                largest = *item;
            }
        }
        largest
    }
    let number_list = vec![34, 50, 25];
    let char_list = vec!['a', 'b', 'c', 'd'];
    println!("Max {}", max(&number_list)); // Max 50
    println!("Max {}", max(&char_list)); // Max d
}

fn lifetime_generics() {
    /// ダングリングを避けるため、関数から借用したものを返す時は中で生成したものの借用の値は返せない
    /// つまり、戻り値は引数のどちらかであるが、どちらの参照なのかわからなくなる
    /// ここでlifetime generics <'X>を使用すると、ライフタイムがXである参照であることを明示できる
    /// 同じライフタイムを指定した場合、短い方が優先される
    fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
        if x.len() > y.len() {
            x
        } else {
            y
        }
    }

    let st1 = String::from("ab");
    {
        let st2 = String::from("abc");
        let res = longest(&st1, &st2);

        println!("{}", res); // abc
    }
}

トレイト

/// traitはインタフェースのようなもの
fn trait_fn() {
    let apple = Apple {};
    let banana = Banana {};
    get_price(&apple); // price: 10
    get_price2(&banana); // price: 5
    info(&apple); // price:10 this is Tree.
}

/// 宣言
trait Fruits {
    fn price(&self) -> u32;
}

trait Plant {
    fn types(&self);
}

/// 実装
struct Apple;

impl Fruits for Apple {
    fn price(&self) -> u32 {
        10
    }
}

impl Plant for Apple {
    fn types(&self) {
        println!("this is tree.")
    }
}

struct Banana;

impl Fruits for Banana {
    fn price(&self) -> u32 {
        5
    }
}

/// genericsでFruitsの実装を指定する
fn get_price<T: Fruits>(fruits: &T) {
    println!("price: {}", fruits.price());
}

/// 引数でFruitsの実装を指定する
fn get_price2(fruits: &impl Fruits) {
    println!("price2: {}", fruits.price());
}

/// 複数traitを指定には+を使用する 引数でなら&(impl Fruits + Plant)
fn info<T: Fruits + Plant>(plant: &T) {
    println!("price: {}", plant.price());
    plant.types();
}

/// where句でTrait境界を後書きもできる
fn info2<T>(plant: &T)
where
    T: Fruits + Plant,
{
    println!("price: {}", plant.price());
    plant.types();
}

クロージャ

let closure1 = |x: i32, y: i32| -> i32 { x + y };
let closure2 = |x| x + 1;

println!("{}", closure1(1, 2) + closure2(3));

単体テスト

https://doc.rust-jp.rs/book-ja/ch11-01-writing-tests.html

struct Rectangle {
   width: u32,
    height: u32,
}
impl Rectangle {
   fn compare_area(&self, other: &Rectangle) -> bool {
      self.width * self.height > other.width * other.height
    }
}

fn double_value(a: i32) -> i32 {
   a * 2
}

fn greeting(name: &str) -> String {
   format!("Hello {} san", name)
}

/* unit testは各ソースの末尾に書くのが基本 */

#[cfg(test)] // cargo testの時だけテストするderive. buildの時に実行しない
mod tests {
   // ファイル内でサブモジュール定義
    use super::*; // 一個親の中身を全て取り込む

    #[test] // テストメソッドであることの通知derive
    fn test_a_is_larger() {
       let a = Rectangle {
          width: 5,
            height: 5,
        };
        let b = Rectangle {
           width: 8,
            height: 3,
        };
        assert!(a.compare_area(&b));
    }

    #[test]
    fn test_double() {
       assert_eq!(6, double_value(3));
    }

    #[test]
    fn test_contains_name() {
       let res = greeting("rust");
        assert!(res.contains("rust"));
    }
}

エラー処理

https://doc.rust-jp.rs/book-ja/ch09-01-unrecoverable-errors-with-panic.html

use std::{
    fs::File,
    io::{self, ErrorKind, Read},
};

/// 失敗する可能性のある値はResult<T, E>で返すことができる
fn result() {
    let f = File::open("hello.txt");
    let f = match f {
        Ok(file) => file,
        Err(error) if error.kind() == ErrorKind::NotFound => {
            // エラー種類を指定
            panic!("There was no such file: {:?}", error); // 処理停止
                                                           // thread 'main' panicked at 'There was no such file: Os { code: 2, kind: NotFound, message: "No such file or directory" }', src/syntax/errors.rs:13:13
                                                           // note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
        }
        Err(error) => {
            // その他のエラー
            panic!("There was a problem opening th file: {:?}", error);
        }
    };
}

/// OptionはOptionalみたいなもの
fn option() {
    fn division_option(x: f64, y: f64) -> Option<f64> {
        if y == 0.0 {
            None
        } else {
            Some(x / y)
        }
    }
    let res: f64 = match division_option(3.0, 0.0) {
        None => panic!("0除算"),
        Some(n) => n,
    };
    println!("{}", res);
}

/// 呼び出し元にエラー処理を移譲する
fn read_username_from_file() -> Result<String, io::Error> {
    let f = File::open("hello.txt");

    let mut f = match f {
        Ok(file) => file,
        Err(e) => return Err(e),
    };

    let mut s = String::new();

    match f.read_to_string(&mut s) {
        Ok(_) => Ok(s),
        Err(e) => Err(e),
    }
}

/// ?演算子は自動でErrをResultとして返してくれる
fn read_username_from_file2() -> Result<String, io::Error> {
    let mut f = File::open("hello.txt")?;
    let mut s = String::new();
    f.read_to_string(&mut s)?;
    Ok(s)
}

// Resultの中身
// enum Result<T, E> {
//     Ok(T),
//     Err(E),
// }

// Optionの中身
// enum Option<T> {
//     None,
//     Some(T),
// }

モジュール

Rustはrustupでインストールし、cargoでパッケージ管理を行う。

package/crate/module

  • package: プロジェクトのようなもの。cargo new で作成できる。
  • crate: ライブラリまたは実行可能ファイルを生成するmoduleツリー。
    • bin crate: srcフォルダ直下に存在する実行可能ファイルmain.rsに対応する。コンパイル後package名と同一のcrateが生成される。
    • lib crate: srcフォルダ直下に存在するlib.rsに対応する。 cargo new --lib を指定で生成され、配布も可能になる。コンパイル後package名と同一のcrateが生成される。binaryファイル名はlib+package名になっている。
  • module: ディレクトリとファイルによるプログラムの階層づけ。

基本的にはlibのみか、bin crateであればmainからlibを呼ぶ形になる。
packageは少なくとも1つのcrateを持たなければならない。(bin or lib)
library crateは1つまでしか存在できない。
binary crateは何個でも存在でき、コマンド引数で実行するcrateを指定する(cargo run --bin rust-lesson)。src/binに配置するとファイルと同名のcrateができる。

curl --proto '=https' --tlsv1.3 https://sh.rustup.rs -sSf | sh # install
cargo new rust-lesson # package生成
cd rust-lesson
cargo check # コンパイルチェック
cargo build # コンパイル (依存関係取り込みも)
cargo run # 実行

cargo doc # ドキュメンテーションを作成 --openでページを同時に開く
cargo test # unittestを実行

パッケージ構造サンプル

mypackage
└── src
    ├── mymodule
    │   └── calc.rs
    ├── mymodule.rs
    ├── lib.rs
    └── main.rs
  • calc.rs
pub fn add(x: i32, y: i32) -> i32 {
    x + y
}
  • mymodule.rs
pub mod calc;

pub fn mymodule_fn() {
    println!("{}", "mymodule_fn");
}
  • lib.rs
// pubは外部から要素が見られるかを指定する
// 設定しないと見られず、modにつけないと階層構造を表現できない
pub mod mymodule;

pub fn lib_fn() {
    println!("{}", "lib_fn");
    println!("{}", mymodule::calc::add(1, 2));
}
  • main.rs
// useはクレートに対して宣言でき、::で繋げられる
use mypackage;
use mypackage::mymodule::calc;
// modは自クレート内のモジュールに対して宣言でき、::は使用できない
mod mymodule;

fn main() {
    println!("{}", calc::add(1, 2));
    mypackage::lib_fn();
    mymodule::mymodule_fn();
}

ライブラリ公開

  1. githubにプロジェクトを公開
  2. dependenciesにXXXXX = {git = "https://github.com/kikusan-16/XXXXX"}とすれば取り込める