コード上で型を確認するためには以下の関数を定義することで確認できる

fn print_typename<T>(_: T) {
    println!("{}", std::any::type_name::<T>());
}

fn main() {
    let s1: &str = "hello";
    print_typename(s1);
}

スカラ(数値)型

符号とその大きさ(ビット数)で表す

符号あり整数型: i, 符号なし整数型: u, 浮動小数点型: f

    • 符号付き整数: i8, i16, i32, i64, i128, isize(ポインタのサイズ)

 

    • 符号無し整数: u8, u16, u32, u64, u128, usize(ポインタのサイズ)

 

    浮動小数点数: f32, f64(浮動小数点型は32, 64ビットのみ)

Bool型

    ブーリアン: bool: trueまたはfalse

文字列型

str

コアライブラリで定義されている文字列型はstrだけ。

strは文字スライス。スライス: メモリ上に存在する文字列データのスタート地点と長さを示す

→ 扱うデータは固定サイズで文字列そのものの変更はできない。

String

Stringは標準ライブラリで定義されている文字列型。文字列データの変更や長さの変更が可能。

標準ライブラリで定義されているStringがよく使う。

strとStringはどちらもUTF-8エンコードされた文字列データを格納する。お互いに型変換可能。

fn main() {
    let s1: String = String::from("hello, world!");
    let s2: &str = &s1; // String -> &str
    let s3: String = s2.to_string(); // &str -> String
    println!("s1: {}, s2: {}, s3: {}", s1, s2, s3)
}

String→&strへの変換はポインタと文字列長をコピーしてスライスを作るためメモリ圧迫はしないが、逆はメモリ確保が行われるので長い文字列の変換は注意が必要。

    • 文字: char: ‘a’, ‘α’, ‘∞’などのUnicodeのスカラー値

 

    文字列: &str: helloなど

参考:ビルド時に値が使用されているかは変数ではなく代入する値が使用されているかが見られる

fn main() {
    let mut foo: String = String::from("hello");
    foo = "Hi!".to_string();

    println!("{}", foo);
}

上記の結果、変数fooはprintln!で使用されているがString::from(“hello”)が使用されていないため以下のメッセージが出力される。

❯ cargo run
warning: value assigned to `foo` is never read
 --> src/main.rs:2:13
  |
2 |     let mut foo: String = String::from("hello");
  |             ^^^
  |
  = note: `#[warn(unused_assignments)]` on by default
  = help: maybe it is overwritten before being read?

warning: `rust-tutorial` (bin "rust-tutorial") generated 1 warning
    Finished dev [unoptimized + debuginfo] target(s) in 0.00s
     Running `target/debug/rust-tutorial`
Hi!

以下のコードであれば警告は出ない。

fn main() {
    let mut foo: String = String::from("hello");
    println!("{}", foo); // ここでString::from("hello");が使用されている

    foo = "Hi!".to_string();
    println!("{}", foo);
}

タプル型

異なる型の値の集合。要素の数は固定。型を後から変更することはできない。

関数で複数の値をまとめて返すときに便利。

内部の値にアクセスするときは.1でアクセス。

let x: (i32, f64, char) = (1, 1.2, 'h');
println!("{:?}", x);
println!("x: {}", x.0)

配列型

同じ型の値の集合。同じ型で要素の数は固定。

内部の値にアクセスするときは[]でアクセス。

let x: [i32, 3] = [1, 2, 3];
println!("{:?}", x);
println!(x: {}, x[0])

配列を参照するときはスライスとして扱われる。よって[start..end]のように範囲指定もできる。

let a: [i32; 3] = [0, 1, 2];
println!("{:?}", &a[1..3]);

ユーザ定義型

Struct

struct Person {
    name: String,
    age: u32,
}

fn main() {
    let person = Person {
        name: "John".to_string(),
        age: 42,
    };
    println!("{}, {}", person.name, person.age);
}

Enum型

enum Event {
    Quit,
    KeyDown(u8),
    MouseDown { x: i32, y: i32 },
}

fn main() {
    let e1 = Event::Quit;
    let e2 = Event::KeyDown(27);
    let e3 = Event::MouseDown { x: 10, y: 20 };

    match e1 {
        Event::Quit => println!("Quit"),
        Event::KeyDown(key) => println!("KeyDown: {}", key),
        Event::MouseDown { x, y } => println!("MouseDown: {}, {}", x, y),
    }
    match e2 {
        Event::Quit => println!("Quit"),
        Event::KeyDown(key) => println!("KeyDown: {}", key),
        Event::MouseDown { x, y } => println!("MouseDown: {}, {}", x, y),
    }
    match e3 {
        Event::Quit => println!("Quit"),
        Event::KeyDown(key) => println!("KeyDown: {}", key),
        Event::MouseDown { x, y } => println!("MouseDown: {}, {}", x, y),
    }
}

Option型

データが存在する場合と、存在しない場合を表現できる列挙型。

データが存在しない場合はNone、存在する場合はその型をTとしたときSome(T)と表現される。

代入はたいていifと合わせて使用され、結果はmatchで参照される。

fn divide(a: i32, b: i32) -> Option<i32> {
    if b == 0 {
        None
    } else {
        Some(a / b)
    }
}

fn main() {
    let mut x: Option<i32> = divide(1, 0);
    match x {
        Some(x) => println!("{}", x),
        None => println!("None"),
    }

    x = divide(10, 2);
    match x {
        Some(x) => println!("{}", x),
        None => println!("None"),
    }
}

Result型

処理の結果が成功かエラーかを表現できる列挙型。

成功の場合はOk(T)、エラーの場合はErr(E)と表現される。

代入はたいていifと合わせて使用され、結果はmatchで参照される。

fn divide(a: i32, b: i32) -> Result<i32, String> {
    if b != 0 {
        Ok(a / b)
    } else {
        Err("Cannot divide by zero".to_string())
    }
}

fn main() {
    let a: Result<i32, String> = divide(1, 0);
    match a {
        Ok(x) => println!("Result is {}", x),
        Err(x) => println!("Error is {}", x),
    }

    let b: Result<i32, String> = divide(10, 2);
    match b {
        Ok(x) => println!("Result is {}", x),
        Err(x) => println!("Error is {}", x),
    }
}

簡略化した書き方

match や if で書くとコード量が多くなるため、unwrap_or() を使う書き方もある。

unwrap_or(),の引数はOkと同じ型の値を代入する。

// ソース
pub fn unwrap_or(self, default: T) -> T {
    match self {
        Ok(t) => t,
        Err(_) => default,
    }
}
fn divide(a: i32, b: i32) -> Result<i32, String> {
    if b != 0 {
        Ok(a / b)
    } else {
        Err("Cannot divide by zero".to_string())
    }
}

fn main() {
    let a: Result<i32, String> = divide(1, 0);
    println!("result: {}", a.unwrap_or(-1));

    let b: Result<i32, String> = divide(10, 2);
    println!("result: {}", b.unwrap_or(-1));
}

Okの場合だけ関数を実行したい場合

and_then(function)を使うfunctionは別途定義が必要

fn divide(a: i32, b: i32) -> Result<i32, String> {
    if b != 0 {
        Ok(a / b)
    } else {
        Err("Cannot divide by zero".to_string())
    }
}

fn func(result: i32) -> Result<i32, String> {
    println!("code: {}", result);
    Ok(result)
}

fn main() {
    let a: Result<i32, String> = divide(1, 0);
    let result_a = a.and_then(func); // 結果はOkなのでfuncは実行される

    let b: Result<i32, String> = divide(10, 2);
    let result_b = b.and_then(func); // 結果はErrなのでfuncは実行されない

    println!("result_a: {:?}", result_a); // -> result_a: Err("Cannot divide by zero")
    println!("result_b: {:?}", result_b); // -> result_b: Ok(5)
}

ベクタ型

配列と似ているが、配列と違い要素の数を変更できる。

fn main() {
    let mut a: Vec<i32> = [1, 2, 3].to_vec();
    // let a: Vec<i32> = vec![1, 2, 3];  でもよい
    // let a: Vec<i32> = vec![0: 5]; で要素と数を指定して定義 -> [0, 0, 0, 0, 0]
    println!("{:?}", a);
    println!("{}", a[0]);

    a.push(34); // 末尾に追加
    println!("{:?}", a);

    a.pop(); // 末尾を削除
    println!("{:?}", a);
}

ただし要素以上の範囲にアクセスしようとするとパニックを起こす。

fn main() {
    let a: Vec<i32> = [1, 2, 3].to_vec();
    // let a: Vec<i32> = vec![1, 2, 3];  でもよい
    // let a: Vec<i32> = vec![0: 5]; で要素と数を指定して定義 -> [0, 0, 0, 0, 0]
    println!("{:?}", a);
    println!("{}", a[3]);
}

// --- 出力結果 ---
// [1, 2, 3]
// thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 3', src/main.rs:10:20
// note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Box型

Boxを使うと値はヒープ領域に確保される。ヒープ領域は確保したいタイミングで必要領域を確保するのでコンパイル時にサイズが分かっていなくても問題ない。

Boxはヒープ領域に任意の型を格納し、スタック領域にヒープ領域へのポインタを置く。

よって、次のようなことができる

    • コンパイル時にサイズが分からない型を格納

 

    • 大きなサイズの方の値を渡す際に、データの中身をコピーせず、ポインタで渡す

 

    共通のトレイトを実装した様々な方を画一的にポインタで扱う(これは意味わからん)
fn main() {
    let byte_array = [b'h', b'e', b'l', b'l', b'o'];
    print(Box::new(byte_array));
}

fn print(s: Box<[u8]>) {
    println!("{:?}", s);
}
广告
将在 10 秒后关闭
bannerAds